c# - 如何使 Task.WhenAny 对 Task 和 CancellationToken 生效?
问题描述
我有交互式任务,在“最坏”场景中根本不执行,因此它由TaskCompletionSource
.
我想等待这个任务完成,或者我收到的令牌被取消——以先发生者为准。这种工作的完美工具是Task.WhenAny
,唯一的问题是它只需要任务,我有一个Task
和一个CancellationToken
。
如何等待(异步,如Task.WhenAny
)触发的第一个事件——完成的任务,或取消的令牌?
async Task MyCodeAsync(CancellationToken token)
{
var tcs = new TaskCompletionSource<UserData>(); // represents interactive part
await Task.WhenAny(tcs.Task, token); // imaginary call
UserData data = tcs.Task.Result; // user interacted, let's continue
...
}
我不创建/管理令牌,所以我无法更改它。我必须处理它。
更新:对于这种特殊情况,可以使用Register
令牌上的方法来取消TaskCompletionSource
. 有关更通用的方法,请参阅 Matthew Watson 的回答。
解决方案
您可以创建一个额外的任务,当取消令牌的等待句柄发出信号时返回:
var factory = new CancellationTokenSource();
var token = factory.Token;
await Task.WhenAny(
Task.Run(() => token.WaitHandle.WaitOne()),
myTask());
(但是,请注意,这虽然很简单,但确实会使用额外的线程,这显然并不理想。请参阅稍后的不使用额外线程的替代解决方案。)
如果要检查哪个任务已完成,则必须在调用之前保留任务的副本,WhenAny()
以便将它们与返回值进行比较,例如:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main()
{
var factory = new CancellationTokenSource(1000); // Change to 3000 for different result.
var token = factory.Token;
var task = myTask();
var result = await Task.WhenAny(
Task.Run(() => token.WaitHandle.WaitOne()),
task);
if (result == task)
Console.WriteLine("myTask() completed");
else
Console.WriteLine("cancel token was signalled");
}
static async Task myTask()
{
await Task.Delay(2000);
}
}
}
如果您不想浪费整个线程等待取消令牌发出信号,则可以使用CancellationToken.Register()
注册一个回调,您可以使用该回调设置 a 的结果TaskCompletionSource
:
(从这里升起)
public static Task WhenCanceled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).SetResult(true), tcs);
return tcs.Task;
}
然后,您可以按如下方式使用它:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main()
{
var factory = new CancellationTokenSource(1000);
var token = factory.Token;
var task = myTask();
var result = await Task.WhenAny(
WhenCanceled(token),
task);
if (result == task)
Console.WriteLine("myTask() completed");
else
Console.WriteLine("cancel token was signalled");
}
public static Task WhenCanceled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).SetResult(true), tcs);
return tcs.Task;
}
static async Task myTask()
{
await Task.Delay(2000);
}
}
}
对于一般情况,这是一种更可取的方法。
推荐阅读
- algorithm - 生成具有某些约束的大量组合列表的计算有效方法是什么?
- python - from scipy.linalg import _fblas: ImportError: DLL load failed: 找不到指定的模块
- c# - 无法加载文件或程序集“Microsoft.Azure.AppConfiguration.AzconfigClient”
- python - 从python验证sql数据库时出错
- postgresql - Postgres:将多个表作为 JSON 哈希返回
- scala - 如何获取(键,值)对中对应键的值
- xcode - 如何更改 Xcode 中的控制台位置?
- node.js - 无法在 AWS Lambda 上将 pdf 转换为图像
- wordpress - 使用 wp_query 使用一个查询搜索多个关键字
- node.js - 使用 JSON 数据解析特定数据