.NET 9 Preview 1 中的 TaskCompletionSource.SetFromTask
Intro
.NET 9 Preview 1 中为 TaskCompletionSource
/TaskCompletionSource<T>
引入了一个 SetFromTask
/TrySetFromTask
的方法,可以从一个已完成的 task 设置 TaskCompletionSource
的结果/状态
New API
namespace System.Collections.Threading.Tasks
{
public partial class TaskCompletionSource
{
public void SetFromTask(Task completedTask);
public bool TrySetFromTask(Task completedTask);
}
public partial class TaskCompletionSource<T>
{
public void SetFromTask(Task<T> completedTask);
public bool TrySetFromTask(Task<T> completedTask);
}
}
Sample
一个简单的使用示例如下:
var tcs = new TaskCompletionSource();
var startTimestamp = TimeProvider.System.GetTimestamp();
_ = Task.Delay(2000).ContinueWith(r => tcs.SetFromTask(r));
await tcs.Task;
var elapsedTime = TimeProvider.System.GetElapsedTime(startTimestamp);
Console.WriteLine($"Completed in {elapsedTime.TotalMilliseconds}ms");
在 Task.Delay
完成之后会设置 TaskCompletionSource
为已完成
再来看一个带返回值 Task
的示例
var tcs = new TaskCompletionSource<int>();
tcs.SetFromTask(Task.FromResult(100));
Console.WriteLine(await tcs.Task);
Console.WriteLine("Completed");
输出结果如下:
看了两个成功完成的示例,接着我们来看下没有成功完成的例子
首先来看一个 task cancelled 的示例
var tcs = new TaskCompletionSource();
using var cts = new CancellationTokenSource(200);
var task = Task.Delay(2000, cts.Token);
await Task.Delay(500);
try
{
tcs.SetFromTask(task);
await tcs.Task;
}
catch (Exception ex)
{
Console.WriteLine(tcs.Task.Status);
Console.WriteLine("Exception:");
Console.WriteLine(ex);
}
Console.WriteLine("Completed");
这里设置了一个 CancellationToken
在 200ms 后 cancel,这里等待了 500ms,之后 task 会因为 cancellationToken 触发变成 cancelled,而 TaskCompletionSource
也会和 task 一样 Status
会变成 Cancelled
,输出结果如下
再来看一个 task 执行异常的示例:
var tcs = new TaskCompletionSource();
var task = Task.Run(() =>
{
throw new NotImplementedException();
});
await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
try
{
tcs.SetFromTask(task);
await tcs.Task;
}
catch (Exception ex)
{
Console.WriteLine(tcs.Task.Status);
Console.WriteLine("Exception:");
Console.WriteLine(ex);
}
Console.WriteLine("Completed");
这里的 task 使用 Task.Run
模拟了一个 task,task 的方法体直接抛出了一个异常,使用 .NET 8 里引入的
.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing)
来等待 task 结束而不抛出异常,可以参考:.NET 8 中的 ConfigureAwaitOptions
输出结果如下:
在 TaskCompletionSource
已经设置了结果之后就不能设置了,否则会引发异常,可以使用 TrySet
来避免直接抛异常,一个简单的示例如下:
var tcs = new TaskCompletionSource();
tcs.SetCanceled();
Console.WriteLine(tcs.Task.Status);
try
{
Console.WriteLine(tcs.TrySetFromTask(Task.CompletedTask));
tcs.SetFromTask(Task.CompletedTask);
}
catch (Exception ex)
{
Console.WriteLine(tcs.Task.Status);
Console.WriteLine("Exception:");
Console.WriteLine(ex);
}
Console.WriteLine("Completed");
这里先将状态设置为了 Cancelled
后面再设置的时候就会失败,我们先用 TrySetFromTask
来看下返回结果,再尝试直接用 SetFromTask
,输出结果如下:
可以看到 TrySetFromTask
返回值是 false
,直接使用 SetFromTask
会引发 InvalidOperationException
异常
More
TaskCompletionSource
在需要自定义 Task,自己包装一个 Task 的时候会比较有用
References
https://github.com/dotnet/runtime/issues/47998
https://github.com/dotnet/runtime/pull/97077
https://github.com/WeihanLi/SamplesInPractice/blob/main/net9sample/Net9Samples/TaskSample.cs