.net核心并行.为每一个问题



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

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

嗯,在。net Core中,web请求必须全部标记为await,这意味着并行。fore_action必须用async标记。但是,标记一个并行。作为async的每个动作意味着我们有一个void async方法,它会引起问题。在我的特殊情况下,这意味着在并行循环中的所有web请求完成之前,响应返回到应用程序,这既尴尬又会导致错误。

问题:使用Parallel有哪些替代方案?ForEach吗?

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

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

(在这里找到:平行。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已经渲染了并行。ForEach实际上是无用的(至少当涉及到嵌套的web调用时)。

感谢您的帮助/建议

为什么Parallel.ForEach不适合此任务在注释中解释:它是为cpu密集型(cpu密集型)任务设计的。如果你把它用于io绑定的操作(比如web请求)——你会浪费线程池,在等待响应的时候线程被阻塞,没有任何好处。仍然可以使用它,但对于这种情况不是最好的。

你需要的是使用异步web请求方法(如HttpWebRequest.GetResponseAsync),但这里有另一个问题-你不想一次执行所有的web请求(如另一个答案建议)。您的列表中可能有数千个url (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();
    }
}

这三种方法都不好。

您不应该在这个场景中使用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);

您应该使用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
}

我认为这段代码可以工作:

for (int i = 0; i < myList.Length; i++)
{
    var item = myList[i];
    var msg = await SendAsync(item.Id);
    //Post Process
}

最新更新