我已经切换到。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)。因此,您可以使用为此设计的线程同步结构,例如Semaphore
。Semaphore
就像队列——它允许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
}