.net List不总是并行运行的原因是什么< Task>?

cnwbcb6i  于 2023-02-10  发布在  .NET
关注(0)|答案(2)|浏览(123)

当我需要一些并行处理时,我通常这样做:

static void Main(string[] args)
{
    var tasks = new List<Task>();
    var toProcess = new List<string>{"dog", "cat", "whale", "etc"};
    toProcess.ForEach(s => tasks.Add(CanRunAsync(s)));
    Task.WaitAll(tasks.ToArray());
}

private static async Task CanRunAsync(string item)
{
    // simulate some work
    await Task.Delay(10000);
}

我遇到过这样的情况,即它不能并行处理这些项,必须使用Task.Run来强制它在不同的线程上运行。
我错过了什么?

5gfr0r5j

5gfr0r5j1#

Task意味着"需要做的事情,可能已经完成,可能正在并行线程上执行,或者可能依赖于进程外数据(套接字等),或者可能只是......连接到某个显示"已完成"的开关"-它与线程处理的关系 * 非常小 *,除了:如果您调度了一个 * continuation *(aka await),那么 * 不知何故 * 它将需要返回到一个线程上才能触发,但这是如何发生的以及这意味着什么取决于创建和拥有该任务的代码。
注意:并行性 * 可以用多个任务来表示 *(如果您选择这样做),但多个任务并不意味着并行性。
在您的情况下:这完全取决于CanRun的功能或特性--而 * 我们并不知道 *。它也可能被称为CanRunAsync

1qczuiv0

1qczuiv02#

我遇到过这样的情况,即它不能并行处理这些项,必须使用Task.Run来强制它在不同的线程上运行。
这些情况很可能与具有异步契约的方法有关,但它们的实现是同步的。例如:

static async Task NotAsync(string item)
{
    Thread.Sleep(10000); // Simulate a CPU-bound calculation, or a blocking I/O operation
    await Task.CompletedTask;
}

任何调用该方法的线程都将被阻塞10秒,然后它将被交回一个已经完成的任务。尽管NotAsync方法的契约是异步的(它有一个可等待的返回类型),但它的实际实现是同步的,因为它在调用期间完成了所有工作。因此,当您试图通过调用该方法创建多个任务时:

toProcess.ForEach(s => tasks.Add(NotAsync(s)));

...当前线程将被阻塞10秒 * 任务数。创建这些任务时,它们都已完成,因此等待它们完成将导致零等待:

Task.WaitAll(tasks.ToArray()); // Waits for 0 seconds

通过将NotAsync Package 在Task.Run中,可以确保当前线程不会被阻塞,因为NotAsync将在ThreadPool上调用。

toProcess.ForEach(s => tasks.Add(Task.Run(() => NotAsync(s))));

Task.Run立即返回一个Task,保证零阻塞。
应该注意的是,编写具有同步实现的异步方法违反了Microsoft的指导原则:

  • 基于TAP的异步方法在返回结果任务之前,可以同步执行少量工作,如验证参数和启动异步操作。同步工作应保持在最低限度,以便异步方法可以快速返回。*

但有时候微软也会违反这条准则。因为违反这条准则比违反not exposing asynchronous wrappers for synchronous methods准则要好。换句话说,为了给人一种异步的印象,暴露内部调用Task.Run的API比阻塞当前线程更罪恶。

相关问题