C# 进程未使用ParallelForEachAsync并行运行
我正在测试通过进程运行python 我的机器有一个2.8GHz的CPU,有4个内核和8个逻辑处理器 我的主控制台应用程序如下C# 进程未使用ParallelForEachAsync并行运行,c#,parallel-processing,parallel.foreach,C#,Parallel Processing,Parallel.foreach,我正在测试通过进程运行python 我的机器有一个2.8GHz的CPU,有4个内核和8个逻辑处理器 我的主控制台应用程序如下 static void Main(string[] args) => MainAsync(args).GetAwaiter().GetResult(); static async Task MainAsync(string[] args) { var startTime = DateTime.UtcN
static void Main(string[] args) => MainAsync(args).GetAwaiter().GetResult();
static async Task MainAsync(string[] args)
{
var startTime = DateTime.UtcNow;
Console.WriteLine($"Execution started at {DateTime.UtcNow:T}");
await ExecuteInParallelAsync(args).ConfigureAwait(false);
Console.WriteLine($"Executions completed at {DateTime.UtcNow:T}");
var endTime = DateTime.UtcNow;
var duration = (endTime - startTime);
Console.WriteLine($"Execution took {duration.TotalMilliseconds} milliseconds {duration.TotalSeconds} seconds");
Console.WriteLine("Press Any Key to close");
Console.ReadKey();
}
其中ExecuteInParallelAsync是执行此工作的方法
private static async Task ExecuteInParallelAsync(string[] args)
{
var executionNumbers = new List<int>();
var executions = 5;
for (var executionNumber = 1; executionNumber <= executions; executionNumber++)
{
executionNumbers.Add(executionNumber);
}
await executionNumbers.ParallelForEachAsync(async executionNumber =>
{
Console.WriteLine($"Execution {executionNumber} of {executions} {DateTime.UtcNow:T}");
ExecuteSampleModel();
Console.WriteLine($"Execution {executionNumber} complete {DateTime.UtcNow:T}");
}).ConfigureAwait(false);
}
如你所见,我要求这个模型执行5次
当我使用调试器时,似乎即使我使用的是ParalellForEach(由AsyncEnumerator包引入),这也不是并行运行的
我认为每个迭代都是在自己的线程上运行的
每个Python模型执行需要5秒钟
并行运行时,我希望整个过程在15秒左右完成,但实际上需要34秒
在调用GetResponse之前和之后添加的Console.WriteLines显示第一个调用正在启动、正在完全执行,然后第二个调用正在启动,等等
这和我呼叫过程有关吗?开始
有人能看出这有什么问题吗
Paul为了让答案有用,这里解释一下异步代码发生了什么。从解释的角度来看,省略了许多不太重要的细节,
ParallelForEachAsync
循环中的代码如下所示:
// some preparations
...
var itemIndex = 0L;
while (await enumerator.MoveNextAsync(cancellationToken).ConfigureAwait(false))
{
...
Task itemActionTask = null;
try
{
itemActionTask = asyncItemAction(enumerator.Current, itemIndex);
}
catch (Exception ex)
{
// some exception handling
}
...
itemIndex++;
}
其中,asyncItemAction
具有类型Func
,它是一个围绕自定义异步操作的包装器,类型为Func
,作为参数传递给ParallelForEachAsync
调用(包装器添加了索引功能)。循环代码只是调用此操作,以获得一个任务,该任务表示异步操作承诺等待其完成。对于给定的代码示例,自定义操作
async executionNumber =>
{
Console.WriteLine($"Execution {executionNumber} of {executions}{DateTime.UtcNow:T}");
ExecuteSampleModel();
Console.WriteLine($"Execution {executionNumber} complete {DateTime.UtcNow:T}");
}
不包含异步代码,但前缀async
允许编译器使用返回一些Task
的方法生成状态机,该方法使代码与循环内的自定义操作调用兼容(从语法角度看)。
重要的是,循环中的代码希望此操作是异步的,这意味着该操作隐式地分为同步部分,同步部分将与asyncItemAction(enumerator.Current,itemIndex)
调用和至少一个(一个或多个取决于内部等待的数量)一起执行可以在迭代其他循环项期间执行的异步部分。下面的伪代码给出了一个想法:
{
... synchronous part
await SomeAsyncOperation();
... asynchronous part
}
在这种特殊情况下,自定义操作中根本没有异步部分,这意味着调用
itemActionTask = asyncItemAction(enumerator.Current, itemIndex);
将同步执行,循环中的下一个迭代将在asyncItemAction
完成整个自定义操作执行之前不会启动
这就是为什么关闭代码中的异步并使用简单的并行性会有所帮助。我不确定ParallelForEachAsync
是如何工作的,因为它不是标准API的一部分,但我认为它需要一些在代码中没有的东西,所以我怀疑它是否能正常工作。由于GetResponse
中没有异步代码,所以不清楚为什么决定使用异步而不是简单的Parallel.ForEach
。删除了我的误导性答案。经过一些额外的测试,结果表明,ParallelForEachAsync
的规则与Parallel.ForEach
的规则不同(我认为它们是等价的)。Dmytro Mukalov关于ParallelForEachAsync
的本质是正确的。在您的情况下,使用Parallel.ForEach
会更好更容易,但是如果您想坚持使用ParallelForEachAsync
请参阅。这是将同步代码转换为异步代码的一般方法。好的,谢谢。我不明白为什么我的问题被降级了,我在里面放了很多信息
itemActionTask = asyncItemAction(enumerator.Current, itemIndex);