C# Parallel.ForEach中的嵌套异步方法

C# Parallel.ForEach中的嵌套异步方法,c#,parallel-processing,async-await,task-parallel-library,parallel.foreach,C#,Parallel Processing,Async Await,Task Parallel Library,Parallel.foreach,我有一个在其中运行多个异步方法的方法。我必须迭代设备列表,并将设备传递给此方法。我注意到这需要很长时间才能完成,因此我考虑使用Parallel.ForEach,以便它可以同时对多个设备运行此过程 假设这是我的方法 public async Task ProcessDevice(Device device) { var dev = await _deviceService.LookupDeviceIndbAsNoTracking(device); var result = awa

我有一个在其中运行多个异步方法的方法。我必须迭代设备列表,并将设备传递给此方法。我注意到这需要很长时间才能完成,因此我考虑使用Parallel.ForEach,以便它可以同时对多个设备运行此过程

假设这是我的方法

public async Task ProcessDevice(Device device) {
    var dev = await _deviceService.LookupDeviceIndbAsNoTracking(device);

    var result = await DoSomething(dev);
    await DoSomething2(dev);
}
然后DoSomething2还调用一个异步方法

public async Task DoSomething2(Device dev) {
    foreach(var obj in dev.Objects) {
        await DoSomething3(obj);
    }
}
随着时间的推移,设备列表不断变大,因此该列表增长越多,程序针对每个设备运行ProcessDevice所需的时间就越长。我想一次处理多个设备。所以我一直在研究使用Parallel.ForEach

程序似乎在设备完全处理之前完成。我还尝试创建一个任务列表,然后为每个设备添加一个运行ProcessDevice的新任务到该列表中,然后等待任务

var listOfTasks = new List<Task>();
foreach(var device in devices) {
    var task = Task.Run(async () => await ProcessDevice(device));
    listOfTasks.Add(task);
}
await Task.WhenAll(listOfTasks);
但在ProcessDevice实际完成运行之前,任务似乎被标记为已完成


请原谅我对这个问题的无知,因为我是并行处理新手,不知道发生了什么。导致这种行为的原因是什么?您是否可以提供任何文档来帮助我更好地了解该怎么做?

在上一个示例中,有几个问题:

var listOfTasks=新列表; 设备中的foreach var设备 { wait Task.Runasync=>wait processDeviceDeviceDevice; } 等待Task.WhenAllistofTasks; 正在执行等待任务。Runasync=>Wait ProcessDevicedevice;意味着在前一个循环完成之前,您不会移动到foreach循环的下一个迭代。基本上,你还是一次做一个

此外,您没有向listOfTasks添加任何任务,因此它保持为空,因此Task.WhenAlllistOfTasks立即完成,因为没有任务等待

试试这个:

var listOfTasks=新列表; 设备中的foreach var设备 { var task=task.Runasync=>Wait ProcessDevicedevice 添加任务; } 等待Task.WhenAllistofTasks;
在上一个示例中,有几个问题:

var listOfTasks=新列表; 设备中的foreach var设备 { wait Task.Runasync=>wait processDeviceDeviceDevice; } 等待Task.WhenAllistofTasks; 正在执行等待任务。Runasync=>Wait ProcessDevicedevice;意味着在前一个循环完成之前,您不会移动到foreach循环的下一个迭代。基本上,你还是一次做一个

此外,您没有向listOfTasks添加任何任务,因此它保持为空,因此Task.WhenAlllistOfTasks立即完成,因为没有任务等待

试试这个:

var listOfTasks=新列表; 设备中的foreach var设备 { var task=task.Runasync=>Wait ProcessDevicedevice 添加任务; } 等待Task.WhenAllistofTasks;
不太确定这是否符合您的要求,但我可以给出如何启动异步进程的示例

 private readonly Func<Worker> _worker;

    private void StartWorkers(IEnumerable<Props> props){
    Parallel.ForEach(props, timestamp => { _worker.Invoke().Consume(timestamp); });
    }

我建议您阅读一下Parallel.ForEach,因为它会对您有所帮助。

如果您要求这样做,我不太确定,但我可以举个例子说明如何启动异步进程

 private readonly Func<Worker> _worker;

    private void StartWorkers(IEnumerable<Props> props){
    Parallel.ForEach(props, timestamp => { _worker.Invoke().Consume(timestamp); });
    }
我建议您阅读一下Parallel.ForEach,因为它会对您有所帮助。

您不能将异步与Parallel.ForEach混合使用。因为您的底层操作是异步的,所以您希望使用异步并发,而不是并行。异步并发最容易用WhenAll表示:

不能将async与Parallel.ForEach混合使用。因为您的底层操作是异步的,所以您希望使用异步并发,而不是并行。异步并发最容易用WhenAll表示:


我可以用Parallel.ForEach解释这个问题。需要了解的一件重要事情是,当wait关键字作用于未完成的任务时,它会返回。如果方法签名允许,它将返回自己的未完成任务,如果它不是无效的。然后由调用方使用该任务对象等待作业完成

但是中的第二个参数是an,它是一个void方法,这意味着不能返回任何任务,这意味着调用程序Parallel.ForEach在本例中无法等待作业完成

因此,在您的情况下,只要点击Wait ProcessDevicedevice,它就会返回,并且没有任何东西等待它完成,因此它开始下一次迭代。当Parallel.ForEach完成时,它所做的只是启动所有任务,而不是等待它们

所以不要在异步代码中使用Parallel.ForEach


斯蒂芬的回答更恰当。您也可以使用WSC的答案,但如果列表较大,则会很危险。一次创建数百或数千个新线程对您的性能没有帮助。

我可以用Parallel.ForEach解释这个问题。需要了解的一件重要事情是,当wait关键字作用于未完成的任务时,它会返回。它会 如果方法签名允许,返回它自己的未完成任务,如果它不是无效的。然后由调用方使用该任务对象等待作业完成

但是中的第二个参数是an,它是一个void方法,这意味着不能返回任何任务,这意味着调用程序Parallel.ForEach在本例中无法等待作业完成

因此,在您的情况下,只要点击Wait ProcessDevicedevice,它就会返回,并且没有任何东西等待它完成,因此它开始下一次迭代。当Parallel.ForEach完成时,它所做的只是启动所有任务,而不是等待它们

所以不要在异步代码中使用Parallel.ForEach



斯蒂芬的回答更恰当。您也可以使用WSC的答案,但如果列表较大,则会很危险。一次创建数百或数千个新线程对您的性能没有帮助。

ProcessDevice是否支持异步?该方法返回与任务相关的:ProcessDevice支持异步?该方法返回一个与任务相关的:我不知道在任务列表中添加所有设备以同时并行化所有设备是否是一个好主意。如果有太多,这将比所有其他解决方案都慢。这将在很大程度上取决于ProcessDevice真正在做什么以及有多少设备,但是是的,应该测试它的性能。另一种不同但相似的方法是将设备分为若干批,比如说,5批并行同步执行每个批。@WSC-对不起,我更新了我的问题,我确实有ListofTask.Addtask;但是在问题中忘记了。@JaronJohnson您更新的代码没有意义。您并没有将任务设置为任何内容。请更仔细地看这个答案中的代码。@GabrielLuci如果在问题中再次写出代码,我应该复制并粘贴我的代码。。再次更新它…我不知道在任务列表中添加所有设备以同时并行所有设备是否是个好主意。如果有太多,这将比所有其他解决方案都慢。这将在很大程度上取决于ProcessDevice真正在做什么以及有多少设备,但是是的,应该测试它的性能。另一种不同但相似的方法是将设备分为若干批,比如说,5批并行同步执行每个批。@WSC-对不起,我更新了我的问题,我确实有ListofTask.Addtask;但是在问题中忘记了。@JaronJohnson您更新的代码没有意义。您并没有将任务设置为任何内容。请更仔细地看这个答案中的代码。@GabrielLuci如果在问题中再次写出代码,我应该复制并粘贴我的代码。。再次更新…当你说你不能将async与Parallel.ForEach混合使用时,我们同意这只是他的情况,而不是一般情况?@Thibaut。我只是写了一个答案来更详细地解释为什么。当你说你不能将async与Parallel.ForEach混合使用时,我们同意这只是他的情况,而不是一般情况?@Thibaut。我刚刚写了一个答案来更详细地解释原因。谢谢你的解释,这很有意义。使用信号量lim来限制并发级别,与这两个答案结合使用是否有助于保持平衡?再一次为我的无知感到抱歉,我对这个问题还不熟悉。我真的需要在这个话题上对自己进行教育,但不确定从哪里开始。你可以,但这可能会使你的情况变得过于复杂。ProcessDevice在做处理器密集型的事情吗?或者它是发出I/O请求读取文件、发出网络请求等?它比处理器更密集。有一些计算发生了,但它是相当简单的。它会发出一些web请求并更新数据库记录。那么Stephen的答案就是最好的方法。发送I/O请求后,Wait将返回,列表中的下一个请求将启动。因此,他们都立即开始,然后开始执行任务。然后,它将等待他们全部完成。它甚至可能发生在同一个线程上。感谢您的解释,这是有意义的。使用信号量lim来限制并发级别,结合这些答案中的任何一个,是否有助于保持平衡?再一次为我的无知感到抱歉,我对这个问题还不熟悉。我真的需要在这个话题上对自己进行教育,但不确定从哪里开始。你可以,但这可能会使你的情况变得过于复杂。ProcessDevice在做处理器密集型的事情吗?或者它是发出I/O请求读取文件、发出网络请求等?它比处理器更密集。有一些计算发生了,但它是相当简单的。它会发出一些web请求并更新数据库记录。那么Stephen的答案就是最好的方法。发送I/O请求后,Wait将返回,列表中的下一个请求将启动。因此,他们都立即开始,然后开始执行任务。然后,它将等待他们全部完成。它甚至可能发生在同一个线程上。
var listOfTasks = devices.Select(ProcessDevice).ToList();
await Task.WhenAll(listOfTasks);