关于.net:多线程设计最佳实践

关于.net:多线程设计最佳实践

Multithreading Design Best Practice

考虑一下这个问题:我有一个程序应该从数据库中获取(比如说)100条记录,然后对于每个记录,它应该从Web服务获取更新的信息。 在这种情况下,有两种方法可以引入并行性:

  • 我在新线程上启动对Web服务的每个请求。 并发线程数由某些外部参数控制(或以某种方式动态调整)。

  • 我创建较小的批处理(假设每个记录有10条记录),并在单独的线程上启动每个批处理(因此以我们的示例为10个线程)。

  • 哪种方法更好,为什么会这样呢?


    选项3最好:

    使用异步IO。

    除非您的请求处理复杂而繁琐,否则您的程序将花费99%的时间等待HTTP请求。

    这正是异步IO的设计目的-让Windows网络堆栈(或.net框架或其他任何东西)担心所有等待,而仅使用一个线程来分派和"拾取"结果。

    不幸的是,.NET框架使它陷入了困境。如果您仅使用原始套接字或Win32 API,则会更容易。无论如何,这是一个使用C#3的(经过测试!)示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    using System.Net; // need this somewhere

    // need to declare an class so we can cast our state object back out
    class RequestState {
        public WebRequest Request { get; set; }
    }

    static void Main( string[] args ) {
        // stupid cast neccessary to create the request
        HttpWebRequest request = WebRequest.Create("http://www.stackoverflow.com" ) as HttpWebRequest;

        request.BeginGetResponse(
            /* callback to be invoked when finished */
            (asyncResult) => {
                // fetch the request object out of the AsyncState
                var state = (RequestState)asyncResult.AsyncState;
                var webResponse = state.Request.EndGetResponse( asyncResult ) as HttpWebResponse;

                // there we go;
                Debug.Assert( webResponse.StatusCode == HttpStatusCode.OK );

                Console.WriteLine("Got Response from server:" + webResponse.Server );
            },
            /* pass the request through to our callback */
            new RequestState { Request = request }  
        );

        // blah
        Console.WriteLine("Waiting for response. Press a key to quit" );
        Console.ReadKey();
    }

    编辑:

    对于.NET,"完成回调"实际上是在ThreadPool线程中触发的,而不是在您的主线程中触发的,因此您仍然需要锁定任何共享资源,但仍然省去了管理线程的所有麻烦。


    有两件事要考虑。

    1.处理记录需要多长时间?

    如果记录处理非常快,则将记录移交给线程的开销可能会成为瓶颈。在这种情况下,您可能希望捆绑记录,这样就不必经常将它们交出。

    如果记录处理可以合理地长时间运行,则差异可以忽略不计,因此较简单的方法(每个线程1条记录)可能是最好的方法。

    2.您计划启动多少个线程?

    如果您不使用线程池,我认为您要么需要手动限制线程数,要么需要将数据分成大块。如果记录数量很大,则为每个记录启动一个新线程将使您的系统崩溃。


    获取平行Fx。看一下BlockingCollection。使用一个线程为它提供批记录,并使用1到n个线程将记录从集合中提取出来以进行服务。您可以控制提供集合的速率以及调用Web服务的线程数。通过ConfigSection使其可配置,并通过提供Action委托集合使其通用,您将拥有一个不错的小批处理程序,可以将其重用于您的内心世界。


    运行该程序的计算机可能不是瓶颈,因此:
    请记住,HTTP协议具有一个keep-alive标头,该标头使您可以在同一套接字上发送多个GET请求,从而避免了TCP / IP握手的麻烦。不幸的是,我不知道如何在.net库中使用它。 (应该是可能的。)

    在回答您的请求时也可能会有延迟。您可以尝试确保始终对服务器有一定数量的未完成请求。


    推荐阅读