.NET 9 中的 TaskCompletionSource.SetFromTask

.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 为已完成

394907b849e1d3c0fb02db085b92a94f.png

success-completed

再来看一个带返回值 Task 的示例

var tcs = new TaskCompletionSource<int>();
tcs.SetFromTask(Task.FromResult(100));
Console.WriteLine(await tcs.Task);
Console.WriteLine("Completed");

输出结果如下:

9a5e3c3ac9bea11f1887e58144feff09.png

success-completed-t

看了两个成功完成的示例,接着我们来看下没有成功完成的例子

首先来看一个 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,输出结果如下

75b1c294617c71bed8f80ca955a2d47d.png

task-cancelled-example

再来看一个 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

输出结果如下:

c23cd6cf942c9d0aec7ca9e0c4ac61b1.png

task-faulted-example

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,输出结果如下:

5b55cc3e302db769017d579ae663935b.png

try-set-from-task-example

可以看到 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

  • .NET 8 中的 ConfigureAwaitOptions

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值