Task
does not maintain a wait handle出于性能原因,只有在代码询问其中一个时才会构造一个。
Task
如何知道它已经完成?
有人会争辩说,实现者在实现中将结果设置在TaskCompletionSource
,但这只能解释现代实现和重写,例如System.IO.FileStream.Begin/EndReadTask
。
我关注Task.IsComplete
财产;几乎在每个实例中,m_stateFlags
方法设置内部按位标志字段(TrySetResult / TrySetException
)以指示任务的状态。
但这并未涵盖所有情况。
这样的方法怎么样?
public async Task FooAsync()
{
await Task.Run(() => { });
}
答案 0 :(得分:3)
一个任务如何知道它已经完成了?
正如我在博客(overview,more detail)中所描述的,有两种任务:委托任务(执行代码)和承诺任务(代表事件)。
委托任务在委托完成时自行完成。
承诺任务是使用TaskCompletionSource<T>
(或BCL内部的等效方法)从外部信号完成的。
答案 1 :(得分:0)
我正在回答我自己的问题,因为我突然想起我知道答案。
使用C#语言支持功能时
它是状态机。
如果异步方法的实现者使用C#语言支持,例如方法声明中的async
关键字和方法体内的await
关键字等待任务内在的操作,那么在他正在实施的任务范围内,状态机通过设置任务的结果来表示任务完成。
例如如果他的实施是这样的:
// client code
public async void TopLevelMethod()
{
await MyMethodAsync();
}
// library code -- his implementation
public async Task MyMethodAsync()
{
await AnotherOperationAsync();
}
然后MyMethodAsync
的完成将委托给编译器生成的状态机。
当然,编译器生成的状态机也将完成AnotherOperationAsync
完成的信令,但这不是重点。
回想一下MoveNext
方法中的状态指示任务完成状态,并且在调用连续回调的MoveNext
内部的块中,它还调用SetResult
上的AsyncXXXMethodBuilder
}。
不使用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(() =>
{
/* Work */
Thread.SpinWait(1000);
tcs.SetResult(...);
autoResetEvent.Set();
};)...;
return tcs.Task;
}
不良案例
等待任务的最佳方法当然是使用await
关键字。但是,如果在实现异步API时,实现者会这样做:
public Task MyMethodAsync()
{
return Task.Run(...);
}
这会让他的API的消费者口口相传,我想?
Task.Run
只应在 fire中使用,并且忘记场景,而您不关心任务完成的时间点。
一个例外是,如果您等待使用Task.Run
关键字调用await
所返回的任务,就像下面显示的代码段一样,在这种情况下,您将使用该语言支持如第一部分所述。
public async Task MyMethodAsync()
{
await Task.Run(...);
}
答案 2 :(得分:0)
从本书CLR通过C#
当硬件设备完成对IRP的处理时,它将排队 完整的IRP进入CLR的线程池。将来的某个时候 线程池线程将提取完成的IRP并执行代码 通过设置异常(如果发生错误)来完成任务 或结果。所以现在Task对象知道操作何时 完成,然后依次运行您的代码,使其可以安全地运行 访问数据。