Task.Delay 不延迟异步方法

Task.Delay not delaying async method

我正在尝试创建多个任务,运行它们并行,并等待它们全部完成。

public class SimulationManager
{
    public List<Task> Simulations = new List<Task>();

    public void AddSimulation(SimulationParameters parameters)
    {
        Simulations.Add(new Task(async () => await new Simulation().Simulate()));
    }

    public async Task StartSimulations()
    {
        Simulations.ForEach(s => s.Start());
        await Task.WhenAll(Simulations);

        Console.WriteLine("All tasks finished");
    }
}

任务本身延迟一秒执行,然后打印出一条消息。

public class Simulation
{
    public async Task Simulate()
    {
        Console.WriteLine("Simulating");
        await Task.Delay(1000);
    }
}

我希望输出为:

Simulating
All tasks finished

相反,我得到:

All tasks finished
Simulating

如果我将 await Task.Delay(1000) 替换为 Thread.Sleep(1000),它会按预期工作。

为什么任务被标记为已完成但实际上并未完成?

如果我读取 Task.WhenAll 之前和之后的任务状态,它正在正确等待。问题是 Task.Delay 没有延迟执行,即使方法是 async.

Simulations.ForEach(s => s.Start());
Console.WriteLine(Simulations.First().Status); // prints "WaitingToRun"
await Task.WhenAll(Simulations);
Console.WriteLine(Simulations.First().Status); // prints "RanToCompletion"
Console.WriteLine("All tasks finished");

你等错了。这是固定版本

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskSimulateEarlyReturn
{
    public class Simulation
    {
        public async Task Simulate()
        {
            Console.WriteLine("Simulating");
            await Task.Delay(1000);
            Console.WriteLine("Finished Simulating");
        }
    }

    public class SimulationManager
    {
        public List<Task<Task>> Simulations = new List<Task<Task>>();

        public void AddSimulation()
        {
            Simulations.Add(new Task<Task>(async () => await new Simulation().Simulate()));
        }

        public async Task StartSimulations()
        {
            for(int i=0;i<4;i++)
            {
                AddSimulation();
            }
            Simulations.ForEach(s => s.Start());
            await Task.WhenAll(Simulations.Select(x=>x.Unwrap()).ToArray());

            Console.WriteLine("All tasks finished");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var man = new SimulationManager();
            man.StartSimulations().Wait();
            Thread.Sleep(1000);
        }
    }
}

关键要素是您正在创建一个任务,其中包含一个异步方法。隐含地,异步方法将始终 return 异步方法完成时完成的任务。由于您将 Task 与 Task 包装在一起,因此您正在等待外部不相关的任务,该任务会立即完成,因此您的竞争条件。

    async void F1()
    {
        await Task.CompletedTask;
    }

    async Task F2()
    {
        await Task.CompletedTask;
    }

两种异步方法是相同的,但是 return void 的 F1 方法仍然是 return 幕后任务,否则您将无法等待它。这只是一个使“旧”代码与异步方法一起工作的编译器技巧,无需将异步打包为它上面的特殊方法签名。

您正在创建这样的任务:

    var t  = new Task(F1);
    var t1 = new Task<Task>(F2);

但是现在您已经将异步任务 returning 方法包装在一个外部任务中,该任务将在异步方法的第一个同步部分完成后立即完成。 您需要做的是等待可以使用 Task.Unwrap 方便地完成的内部任务,正是出于这个原因。

如果您在我的示例中删除 Task.Unwrap 调用,您正在等待外部任务并且您正在恢复您的竞争条件。

通常我不会使用 async/await 除非释放 UI 线程。 async/await 本质上是单线程的,因为你只能等待一个任务。如果以这种方式使用(Task.WhenAll 有点作弊),您获得的唯一功能就是跳线。在某个时间点,您的异步等待事物将在一个线程上 运行,这可能会根据同步上下文在等待期间和之后发生变化。

删除多余的任务包装器 - 这就是导致问题的原因。
将模拟存储为返回 Task 的函数,并通过调用它明确地开始模拟。

public class SimulationManager
{
    public List<Func<Task>> Simulations = new List<Func<Task>>();

    public void AddSimulation(SimulationParameters parameters)
    {
        Simulations.Add(() => new Simulation().Simulate());
    }

    public async Task StartSimulations()
    {
        var tasks = Simulations.Select(simulate => simulate()).ToArray();
        await Task.WhenAll(tasks);

        Console.WriteLine("All tasks finished");
    }
}