我在回答自己的问题,因为我突然想起来我知道答案了。
当使用C#语言支持特性时
这是状态机。
如果异步方法的实现者使用了C#语言支持的特性,例如在方法声明中使用async
关键字,在方法体内使用await
关键字等待与任务本质相关的操作,则就实现的任务而言,状态机通过设置任务结果来发出任务完成信号。
例如他的实现如下:
public async void TopLevelMethod()
{
await MyMethodAsync();
}
public async Task MyMethodAsync()
{
await AnotherOperationAsync();
}
然后,MyMethodAsync
的完成将被委托给编译器生成的状态机。
当然,AnotherOperationAsync
完成的信号也将由编译器生成的状态机处理,但这不是重点。
回想一下,在 MoveNext
方法内部的状态指示任务完成状态,并且在调用继续回调的块内部,它还在 AsyncXXXMethodBuilder
上调用了 SetResult
。
当不使用 C# 语言支持功能时
然而,如果异步方法的实现者没有使用 C# 语言特性,则实现者有责任通过在 TaskCompletionSource
对象上设置相关结果、异常或取消属性来发出任务完成的信号。
例如:
public Task MyMethodAsync()
{
var tcs = new TaskCompletionSource<object>();
try
{
AnotherOperation();
tcs.SetResult();
}
catch(Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
如果实现者没有使用TPL支持或使用旧的.NET API异步调用另一个操作,那么实现者有责任通过其中一个Try/SetResult/Exception
等方法显式地设置任务状态来信号任务完成。
例如:
public Task MyMethodAsync()
{
var tcs = new TaskCompletionSource...
var autoReseEvent = ...
ThreadPool.QueueUserWorkItem(new WaitCallback(() =>
{
Thread.SpinWait(1000);
tcs.SetResult(...);
autoResetEvent.Set();
};)...;
return tcs.Task;
}
一个不明智的案例
等待任务的最佳方式当然是使用await
关键字。然而,如果在实现异步API时,实施者这样做:
public Task MyMethodAsync()
{
return Task.Run(...);
}
这会让他的API使用者感到不满意,我想是吧?
Task.Run
只应在您不关心任务何时完成的情况下,用于fire and forget场景。
唯一的例外是,如果您使用await
关键字等待调用Task.Run
返回的任务,就像下面显示的代码片段一样,那么您将使用第一部分中描述的语言支持。
public async Task MyMethodAsync()
{
await Task.Run(...);
}