.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; }