.net Core Parallel.ForEach问题

时间:2016-09-30 14:59:15

标签: c# parallel-processing

我已经切换到某些项目的.net Core,现在我遇到了Parallel.ForEach的问题。在过去,我经常有一个id值列表,然后我将用它来发送Web请求以获取完整数据。它看起来像这样:

Parallel.ForEach(myList, l =>
{
    // make web request using l.id 
    // process the data somehow
});

好吧,在.net Core中,必须标记所有网页请求await,这意味着必须使用async标记Parallel.ForEach操作。但是,将Parallel.ForEach操作标记为async意味着我们有一个导致问题的void async方法。在我的特殊情况下,这意味着响应返回到应用程序之前并行循环中的所有Web请求都已完成,这既尴尬又会导致错误。

问题:在这里使用Parallel.ForEach有哪些替代方案?

我发现一个可能的解决方案是将Parallel循环包装在Task中并等待任务:

await Task.Run(() => Parallel.ForEach(myList, l =>
{
    // stuff here
}));

(在此处找到:Parallel.ForEach vs Task.Run and Task.WhenAll

但是,这对我不起作用。当我使用它时,我仍然在循环完成之前返回应用程序。

另一种选择:

var tasks = new List<Task>();
foreach (var l in myList)
{
    tasks.Add(Task.Run(async () =>
    {
         // stuff here
    }));
}
await Task.WhenAll(tasks);

这似乎有效,但这是唯一的选择吗?似乎新的.net Core已经使Parallel.ForEach几乎无用(至少在嵌套的Web调用时)。

感谢任何帮助/建议。

4 个答案:

答案 0 :(得分:16)

注释中解释了为什么Parallel.ForEach不适合此任务:它专为CPU绑定(CPU密集型)任务而设计。如果你将它用于IO绑定操作(比如发出web请求) - 你会在等待响应时浪费线程池线程,没有任何好处。它仍然可以使用它,但这不适合这种情况。

您需要的是使用异步Web请求方法(如HttpWebRequest.GetResponseAsync),但这里还有另一个问题 - 您不希望立即执行所有Web请求(如另一个答案所示)。您的列表中可能有数千个网址(ID)。因此,您可以使用为此设计的线程同步构造,例如SemaphoreSemaphore就像队列一样 - 它允许X线程通过,其余的应该等到其中一个繁忙线程完成它的工作(稍微简化一下)。这是一个例子:

static async Task ProcessUrls(string[] urls) {
    var tasks = new List<Task>();
    // semaphore, allow to run 10 tasks in parallel
    using (var semaphore = new SemaphoreSlim(10)) {
        foreach (var url in urls) {
            // await here until there is a room for this task
            await semaphore.WaitAsync();
            tasks.Add(MakeRequest(semaphore, url));
        }
        // await for the rest of tasks to complete
        await Task.WhenAll(tasks);
    }
}

private static async Task MakeRequest(SemaphoreSlim semaphore, string url) {
    try {
        var request = (HttpWebRequest) WebRequest.Create(url);

        using (var response = await request.GetResponseAsync().ConfigureAwait(false)) {
            // do something with response    
        }
    }
    catch (Exception ex) {
        // do something
    }
    finally {
        // don't forget to release
        semaphore.Release();
    }
}

答案 1 :(得分:9)

这3个apporaches都不好。

您不应在此方案中使用Parallel类或Task.Run

相反,有一个async处理程序方法:

private async Task HandleResponse(Task<HttpResponseMessage> gettingResponse)
{
     HttpResponseMessage response = await gettingResponse;
     // Process the data
}

然后使用Task.WhenAll

Task[] requests = myList.Select(l => SendWebRequest(l.Id))
                        .Select(r => HandleResponse(r))
                        .ToArray();

await Task.WhenAll(requests);

答案 2 :(得分:1)

您应该使用ref关键字调用方法以完成工作,并且应该以最小的努力完成这项工作。在类似情况下,这种方法对我来说效果很好。

Parallel.ForEach(myList, l =>
{

    // make web request using ref l.id 
    string id=l.id;
    WebRequest webRequest= MakeRequest(ref id);
    // process the data somehow
});

private WebRequest MakeRequest(ref string id)
{
  //make and return web request
}

答案 3 :(得分:-1)

我认为此代码将起作用:

for (int i = 0; i < myList.Length; i++)
{
    var item = myList[i];

    var msg = await SendAsync(item.Id);
    //Post Process
}