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");
}
}
我正在尝试创建多个任务,运行它们并行,并等待它们全部完成。
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");
}
}