C# 将异步方法传递到Parallel.ForEach
我在读关于Parallel.ForEach的文章,其中提到“Parallel.ForEach与传入异步方法不兼容” 因此,为了检查,我编写了以下代码:C# 将异步方法传递到Parallel.ForEach,c#,async-await,parallel.foreach,C#,Async Await,Parallel.foreach,我在读关于Parallel.ForEach的文章,其中提到“Parallel.ForEach与传入异步方法不兼容” 因此,为了检查,我编写了以下代码: static async Task Main(string[] args) { var results = new ConcurrentDictionary<string, int>(); Parallel.ForEach(Enumerable.Range(0, 100), async index =>
static async Task Main(string[] args)
{
var results = new ConcurrentDictionary<string, int>();
Parallel.ForEach(Enumerable.Range(0, 100), async index =>
{
var res = await DoAsyncJob(index);
results.TryAdd(index.ToString(), res);
});
Console.ReadLine();
}
static async Task<int> DoAsyncJob(int i)
{
Thread.Sleep(100);
return await Task.FromResult(i * 10);
}
static async Task Main(字符串[]args)
{
var结果=新的ConcurrentDictionary();
Parallel.ForEach(Enumerable.Range(01100),异步索引=>
{
var res=等待DoAsyncJob(索引);
results.TryAdd(index.ToString(),res);
});
Console.ReadLine();
}
静态异步任务DoAsyncJob(int i)
{
睡眠(100);
返回等待任务。FromResult(i*10);
}
此代码同时填充结果
字典
顺便说一下,我创建了一个类型为concurrentdirectionary
的字典,因为如果我在调试模式下探索它的元素,我会看到元素是按键排序的,我认为元素是被添加的
我想知道我的代码是否有效?如果它“与传入异步方法不兼容”,为什么它工作得很好?异步方法是启动并返回任务的方法
你的代码在这里
Parallel.ForEach(Enumerable.Range(0, 100), async index =>
{
var res = await DoAsyncJob(index);
results.TryAdd(index.ToString(), res);
});
并行运行异步方法100次。也就是说,它并行于任务创建,而不是整个任务。当ForEach
返回时,您的任务正在运行,但不一定完成
您的代码之所以有效,是因为DoAsyncJob()
实际上不是异步的-您的任务在返回时就完成了Thread.Sleep()
是一种同步方法Task.Delay()
是它的异步等价物
理解两者之间的区别。正如其他人已经指出的那样,并行(和Parallel.ForEach
)用于CPU限制的操作,异步编程是不合适的。此代码之所以有效,是因为DoAsyncJob
不是真正的异步方法<代码>异步
不会使方法异步工作。等待一个完成的任务,如任务返回的任务。FromResult
也是同步的async Task Main
不包含任何异步代码,这将导致编译器警告
演示
Parallel.ForEach
如何与异步方法一起工作的示例应调用真正的异步方法:
static async Task Main(string[] args)
{
var results = new ConcurrentDictionary<string, int>();
Parallel.ForEach(Enumerable.Range(0, 100), async index =>
{
var res = await DoAsyncJob(index);
results.TryAdd(index.ToString(), res);
});
Console.WriteLine($"Items in dictionary {results.Count}");
}
static async Task<int> DoAsyncJob(int i)
{
await Task.Delay(100);
return i * 10;
}
没有接受Func
的重载,它只接受操作
委托。这意味着它不能等待任何异步操作
异步索引
被接受,因为它是隐式的。就Parallel.ForEach
而言,它只是一个操作
结果是,
Parallel.ForEach
触发100个任务,从不等待它们完成。这就是为什么应用程序终止时字典仍然为空。如果已经有异步工作,则不需要并行。ForEach
:
static async Task Main(string[] args)
{
var results = await new Task.WhenAll(
Enumerable.Range(0, 100)
Select(i => DoAsyncJob(I)));
Console.ReadLine();
}
关于异步作业,您可以一路异步:
static async Task<int> DoAsyncJob(int i)
{
await Task.Delay(100);
return await Task.FromResult(i * 10);
}
静态异步任务DoAsyncJob(int i)
{
等待任务。延迟(100);
返回等待任务。FromResult(i*10);
}
更好的是:
static async Task<int> DoAsyncJob(int i)
{
await Task.Delay(100);
return i * 10;
}
静态异步任务DoAsyncJob(int i)
{
等待任务。延迟(100);
返回i*10;
}
或者根本没有:
static Task<int> DoAsyncJob(int i)
{
Thread.Sleep(100);
return Task.FromResult(i * 10);
}
静态任务DoAsyncJob(int i)
{
睡眠(100);
返回任务.FromResult(i*10);
}
不要这样做<代码>并行。ForEach用于CPU密集型计算,不识别异步方法。它不会等待它们,本质上是将它们转换为async void
fire-and-forget调用。您的方法无论如何都不是异步的,因此无法说出正确的调用的外观like@PanagiotisKanavos请公布你的信息来源。谢谢我想知道我的代码是否有效?
否。为什么它工作得很好?
它不工作,但您没有意识到,因为a)它不执行任何异步工作。并行。ForEach
-“安排多个线程并行执行以下工作”异步“好吧,在其他东西完成这个等待的任务之前,这个线程没有什么有用的工作要做。”即使它真的工作了(它没有,正如Panagitotis所说,它是完全同步的),您以一种奇怪的方式组合东西,过度分配资源,然后忽略它们。@Seabizkit没有什么可说服的。没有接受任务的重载。没有重载,就没有办法等待任何异步调用。async
不会等待任何东西,也不会使任何东西异步运行。async void
调用不能等待,这就是为什么它们被认为是事件处理程序之外的bug。您在评论中说我可以使用list.Select(item=>Task.Run(…)为什么我应该在Select方法中使用Task.Run()?我可以这样使用:var tasks=Enumerable.Range(0,100)。Select(async index=>{var res=await-DoAsyncJob(index);results.TryAdd(index.ToString(),res);});await-Task.WhenAll(tasks);Async用于释放等待“I/O”的线程"东西,所以在这种情况下,它不会释放一些线程,因为foreach中的操作可能是db访问,例如…你是说如果调用程序aka Parallel.foreach可以返回任务,而事实上它没有返回任务,那么情况就是这样的…所有并行的任务操作都不能正确地实现异步库。是吗我说可以吗?@DmitryS因为你没有解释你真正想做什么。如果你想在后台处理一些数据项而不阻塞,你可以使用。选择(…=>Task.Run…)
。如果您想测试多个算法并获得第一个结果,同样可以。@SeabizkitParallel.ForEach
专门用于数据并行。这就是为什么它不为任务提供任何重载,而是实际使用当前线程进行处理的原因。它
static Task<int> DoAsyncJob(int i)
{
Thread.Sleep(100);
return Task.FromResult(i * 10);
}