我正在测试通过Process.Start并行运行python
我的机器有一个2.8GHz CPU,带有4个内核和8个逻辑处理器
我的主控制台应用程序如下
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);
}
ExecuteSampleModel运行Python模型...
IModelResponse GetResponse()
{
_actualResponse = new ModelResponse();
var fileName = $@"main.py";
var p = new Process();
p.StartInfo = new ProcessStartInfo(@"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\python.exe", fileName)
{
WorkingDirectory = RootFolder,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
p.Start();
_actualResponse.RawResponseFromModel = p.StandardOutput.ReadToEnd();
p.WaitForExit();
return _actualResponse;
}
如您所见,我要求该模型执行5次
当我使用调试器时,即使我使用的是ParalellForEach(由AsyncEnumerator软件包引入),它似乎也不会并行运行
我认为每次迭代都在自己的线程上运行吗?
每个Python模型执行需要5秒钟。
并行运行,我希望整个过程能在15秒左右的时间内完成,但实际上需要34秒。
在调用GetResponse之前和之后添加的Console.WriteLines显示,第一个调用正在开始,已完全执行,然后第二个正在启动,依此类推
这跟我打电话给Process.Start有关吗?
有人可以看到这有什么问题吗?
保罗
答案 0 :(得分:1)
在这里使答案有用的是解释异步代码发生了什么。从解释的角度来看,省略了很多不太重要的细节,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<T, long, Task>
,它是类型为Func<T, Task>
的自定义异步操作的包装器,该包装器作为参数传递给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)
调用以及至少一个(一个或多个,取决于{ {1}}内部)异步部分,可以在其他循环项上进行迭代期间执行。以下伪代码给出了一个想法:
awaits
在这种情况下,自定义操作中根本没有异步部分,因此意味着调用
{
... synchronous part
await SomeAsyncOperation();
... asynchronous part
}
将同步执行,并且直到 itemActionTask = asyncItemAction(enumerator.Current, itemIndex);
完成整个自定义操作执行后,循环内的下一次迭代才会开始。
这就是为什么关闭代码中的异步并使用简单的并行性会有所帮助的原因。