【问题标题】:How to process multiple threads at once using Task Parallel Library如何使用任务并行库一次处理多个线程
【发布时间】:2014-07-22 09:47:17
【问题描述】:

我使用的是 Asp.Net 4.0。

我还有一个 HashSet,我想从中异步创建和运行任务,等待它们全部完成并应用超时。下面的代码是我目前所拥有的,但是考虑到使用 TPL 给猫剥皮的方法有很多,这是否可以改进,或者是否存在任何关键问题?

        TaskWrapper[] taskWrappers = new TaskWrapper[nodeCount];
        Task<ResponseWrapper>[] tasks = new Task<ResponseWrapper>[nodeCount];

        int i = 0;
        foreach (var n in rt.Nodes)
        {
            tasks[i] = Task<ResponseWrapper>.Factory.StartNew(() => MyMethod.Submit(n.Data, timeout));

            taskWrappers[i].LeadPointID = n.LeadPointID;
            // other wrapper stuff;
            taskWrappers[i].Task = tasks[i];

            i++;
        }

        Task.WaitAll(tasks, timeout);

阅读好心提供的文章后,似乎这种代码结构将是首选的工作方法:

class MyService2
{
    public int CalculateMandelbrot()
    {
        // Tons of work to do in here!
        for (int i = 0; i != 10000000; ++i)
            ;
        return 42;
    }
}

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.CalculateMandelbrot());
}

但是,正如我之前指出的,我无权访问 VS2012,因此作为起点,是否有与此模式等效的 VS2010?

【问题讨论】:

  • 如果一切都按要求工作,感觉应该在 codereview.stackexchange.com 上。
  • 使用 C# 4(.NET 4.0 附带)你应该小心capturing foreach loops variables
  • 看起来不错。没有错。尽管异步现在风靡一时,但使用同步阻塞调用尤其没有错。我会用功能性Select(例如rt.Nodes.Select(n =&gt; ...))替换命令式循环。
  • @usr 但是阻塞调用意味着线程在调用期间变得无用。你在浪费资源。这很容易避免。怎么没毛病?
  • @dcastro 它所做的只是节省内存(主要是托管堆栈)。如果你有可用的内存,那么保存它的好处是零。

标签: c# multithreading task-parallel-library


【解决方案1】:

Task.WaitAll 将阻塞你的主线程,直到所有其他任务完成。可用于处理更多请求的线程少了一个。

相反,您应该使用await Task.WhenAll。但是,WhenAll 没有需要超时的重载。 A possible workaround 是将其与Task.Delay 结合,然后等待所有任务完成或延迟任务完成:

await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));

如果您无权访问 VS 2012,则另一种方法是将继续任务附加到 WhenAny,然后返回该任务。框架将负责其余的工作。

public Task<string> Post()
{
    var tasks = //...
    var continuation = Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout))
                           .ContinueWith(whenAnyTask => 
        {
             //the rest of your code
             return "Hello";
        };

    return continuation;
}

更重要的是:不要在 ASP.NET 应用程序上使用Task.Run(或StartNew)!

您正在从线程池中获取线程以更快地为客户端提供服务,但这些线程可以用于为更多个客户端提供服务!所以你是在告诉其他客户“等一下,我将把我的资源集中在首先为这个人服务上”。您还引入了很多不必要的开销。

在网络应用程序中,最好一次只使用一个一个线程来为客户端提供服务,并且只将异步代码用于异步 I/O。


编辑:阿隆

.net (x86) 中的每个线程使用 1MB 的 RAM 或 (x64) 上的 4MB。因此,即使是具有 16GB 内存的相当强大的服务器也只能运行 4000 个线程(假设在其他任何地方都没有使用内存)。也就是说,如果每个请求运行 4 个线程,则只能同时处理 1000 个请求。

而 node.js 服务器(使用与 ASP.Net async/await 类似的模型)能够轻松地在单个线程上处理超过 10000 个并发请求(读取的内存占用要小得多)。


有关更详细的说明,请参阅 Stephen Cleary 的 Task.Run Etiquette Examples: Don't Use Task.Run in the Implementation

【讨论】:

  • 我相信在 VS2010/Asp.Net 4.0 中我无法使用“等待”
  • @dotnetnoob 如果你升级到 vs2012,你可以。 Async Targetting Pack 在 .NET 4.0 上启用异步/等待。即使您不想升级,您的代码也需要返工。您应该在一个线程中同步执行所有操作,并且仅将任务用于异步 I/O。请参阅我的更新答案。
  • 如果你知道我想要达到什么目标可能会更清楚一点。我需要为每个用户向第三方服务发出一些 Http POST 请求。有些是同步的,有些是异步的,我需要等待响应。
  • @dotnetnoob 即便如此,您也不应该使用StartNew。如果这些 HTTP 请求是异步的(应该如此),API 将返回一个任务。您的 MyMethod.Submit 方法应该将该任务返回给控制器,然后控制器直接等待该任务。
  • @dcastro 然而,我会说,而不是“你应该在一个线程中同步地做所有事情,并且只将任务用于异步 I/O。”我会说你应该异步地做所有的单线程。问题是,多年的教条给我们留下了将异步等同于多线程的整整一代开发人员。再一次……请参阅 Stephen Cleary 和 Jon Skeet 的主题。
猜你喜欢
  • 2011-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-30
  • 1970-01-01
  • 1970-01-01
  • 2021-07-05
相关资源
最近更新 更多