.NET的TaskCompletionSource类有什么用?
最近在看同事代码的时候,看到了一个之前从来没用过的类TaskCompletionSource,研究了一下这个类是干嘛的,在此记录一下。
TaskCompletionSource类的作用
简单来说,TaskCompletionSource类提供了一种手动控制Task完成状态的方式。
回想一下我们平时是怎么使用Task的。在创建Task时,必须提供一个delegate,表示需要执行什么操作。然后,这个操作会被自动执行,直到完成后,Task会被自动设置上相应的完成状态,如RanToCompletion, Faulted或者Canceled。
我们可以创建一个什么都不干的Task,然后根据情况灵活地设置它的完成状态吗?用Task类是无法实现的,但是用TaskCompletionSource类可以。下面是一个简单的例子。
在创建TaskCompletionSource时,内部会创建一个什么都不干的Task。生产者可以通过TaskCompletionSource的SetResult等方法设置Task的完成状态,而消费者可以在生产者设置完Task的完成状态后获取结果。
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task<int> task = tcs.Task;
// Start a background task that will complete tcs.Task
Task.Run(() =>
{
Thread.Sleep(1000);
tcs.SetResult(15);
});
// The attempt to get the result of task blocks the current thread until the completion source gets signaled
Stopwatch sw = Stopwatch.StartNew();
int result = task.Result;
sw.Stop();
// ElapsedTime should be ~1000ms, task.Result should be 15
Console.WriteLine("(ElapsedTime={0}): task.Result={1}", sw.ElapsedMilliseconds, result);
在TaskCompletionSource中,有六个方法可以用来设置Task的完成状态:
SetResult和TrySetResult:设置为RanToCompletion状态SetException和TrySetException:设置为Faulted状态SetCanceled和TrySetCanceled:设置为Canceled状态
SetXXX和TrySetXXX的区别在于,重复调用SetXXX会抛异常,但重复调用TrySetXXX不会。
TaskCompletionSource类中的所有成员都是线程安全的。
源码解析
TaskCompletionSource的源码比较简单,主要功能是通过调用Task的internal方法实现的。
-
创建TaskCompletionSource时,内部会创建一个啥都不干的Task
private readonly Task<TResult> _task; public TaskCompletionSource() => _task = new Task<TResult>(); -
可以通过属性获取该内部Task
public Task<TResult> Task => _task; -
设置内部Task的完成状态是通过调用Task的方法实现的
public bool TrySetResult(TResult result) { bool rval = _task.TrySetResult(result); if (!rval) { _task.SpinUntilCompleted(); } return rval; }