以下代码按预期工作 - 两个服务同时启动。
List<Service> services = [ new Service(), new Service() ];
foreach (Service service in services)
{
Task task = service.StartAsync();
}
Console.ReadLine();
class Service
{
public async Task StartAsync()
{
await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("HH:mm:ss")}\tStarted");
await Task.Delay(TimeSpan.FromSeconds(1));
//Simulate some synchronous work.
Thread.Sleep(10_000);
await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("HH:mm:ss")}\tFinished");
}
}
输出是:
20:32:25 Started
20:32:25 Started
20:32:35 Finished
20:32:35 Finished
但是当你将延迟从
TimeSpan.FromSeconds(1)
更改为 TimeSpan.FromMicroseconds(1)
时,输出为:
20:40:10 Started
20:40:20 Finished
20:40:20 Started
20:40:30 Finished
为什么改变延迟会影响行为?是否存在竞争条件?
但是当您将延迟从
更改为TimeSpan.FromSeconds(1)
...TimeSpan.FromMicroseconds(1)
.NET 计时器无法在亚毫秒时间跨度内触发。任何小于一毫秒的时间跨度都会蒸发为零:
Task task = Task.Delay(TimeSpan.FromMicroseconds(999));
Console.WriteLine(ReferenceEquals(task, Task.CompletedTask)); // True
在线演示.
因此
await Task.Delay(TimeSpan.FromMicroseconds(1));
变为 await Task.CompletedTask;
,这是一个空操作。执行流程同步继续。没有安排。
await Console.Out.WriteLineAsync
也是同步,因为 Console.Out
不会覆盖异步 TextWriter
/Stream
API 的默认实现,这些 API 都是同步实现的。
结果是你的
StartAsync
方法变得完全同步。除了它的名称和签名之外,它没有任何异步内容。它里面的所有内容都在当前线程上同步运行。如果您想确保服务的执行是并行的,您可以使用 Task.Run
,如下所示:
foreach (Service service in services)
{
Task task = Task.Run(() => service.StartAsync());
}
这是有效的,因为
Task.Run
将 function
委托的调用卸载给 ThreadPool
。