原文地址:devblogs.microsoft.com/oldnewthing…
原文作者:devblogs.microsoft.com/oldnewthing…
发布时间:2020年7月1日
Windows Runtime中有很多异步操作,典型的处理方式是在操作完成后安排继续。根据语言的不同,可能是Promise.then或task.then(),也可能是 await或co_await。它们都归结为 "立即从函数中返回,并在这个异步事物产生一个值时继续执行"。
Windows Runtime异步操作可以被取消,然后事情就变得很有趣了。
调用Cancel方法,从技术上讲,只是一个请求,而不是一个需求。建议操作支持取消,但并不是严格要求。一个操作可能会选择直接忽略你的取消请求。或者它可以通过使操作立即完成部分结果来处理取消请求。
但是,让我们假设操作接受了你的取消请求,并且确实以Canceled的状态完成了操作。
你的代码看到的取消结果是什么?
让我们从C#开始。为了取消操作,你必须将其包裹在一个任务中,然后取消任务。
var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));
StorageFile file;
try {
file = await picker.PickSingleFileAsync().AsTask(cts.Token);
} catch (TaskCanceledException) {
file = null;
}
if (file != null) {
DoSomething(file);
}
我们在三秒后取消文件拾取器对话框。这是由PickSingleFileAsync()返回的AsyncOperation来完成的,将其转换为一个带有AsTask的Task,并将其与一个已配置为三秒后取消的取消令牌源相关联。
当这个操作被取消时,你会得到一个TaskCanceledException。这是Windows Runtime异步操作完成时状态为Canceled的结果。然后,C#投影将其转换为TaskCanceledException。
这是C#异步代码所期望的行为,所以C#将Windows Runtime异步操作投射到任务中的行为很自然。
下一次,我们再来看看C++/CX与PPL。
额外的唠叨。你也可以通过直接与IAsyncOperation对话来取消任务,而不是将其转换为C#任务。
async void CancelAfter(IAsyncInfo info, TimeSpan delay)
{
await Task.delay(delay);
info.Cancel();
}
var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
var op = picker.PickSingleFileAsync();
CancelAfter(op, TimeSpan.FromSeconds(3));
file = await op;
} catch (TaskCanceledException) {
file = null;
}
if (file != null) {
DoSomething(file);
}
你可以尝试通过将CancelAfter代码内联来赚取一些风格点。
var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
var op = picker.PickSingleFileAsync();
((Action)(async () => { await Task.Delay(TimeSpan.FromSeconds(3)); op.Cancel(); }))();
file = await op;
} catch (TaskCanceledException) {
file = null;
}
或者也许更有用,让CancelAfter返回原来的异步操作,这样你就可以一次性取消它并等待它。
public static class Helpers
{
static async void CancelAfterHelper(IAsyncInfo info, TimeSpan delay)
{
await Task.Delay(delay);
info.Cancel();
}
static public IAsyncAction CancelAfter(this IAsyncAction action, TimeSpan delay)
{
CancelAfterHelper(action, delay);
return action;
}
static public IAsyncOperation<T>
CancelAfter<T>(this IAsyncOperation<T> op, TimeSpan delay)
{
CancelAfterHelper(op, delay);
return op;
}
}
var picker = new FileOpenPicker { FileTypeFilter = { ".txt" } };
StorageFile file;
try {
file = await picker.PickSingleFileAsync().CancelAfter(TimeSpan.FromSeconds(3));
} catch (TaskCanceledException) {
file = null;
}