深入探究 WinRT 和 await

在最近发布的使用 Windows 运行时中异步性来始终保持应用程序能够快速流畅地运行这篇博文中,包含了一些如何在 C# 和 Visual Basic 中使用await 关键字的示例,允许开发人员在使用 WinRT 异步操作的同时,保持和推导良好的控制流。

在接下来的博文中,我将更加深入地介绍 await 在 WinRT 中的工作原理。这些知识将帮助您更轻松地推导使用 await 的代码,进而帮助您编写更出色的 Metro 风格应用程序。

首先,我们先来审视一下没有 await 的情况。

基础知识回顾

WinRT 中的所有异步功能全部源自同一个接口:IAsyncInfo

public interface IAsyncInfo
{
    AsyncStatus Status { get; }
    HResult ErrorCode { get; }
    uint Id { get; }

    void Cancel();
    void Close();
}

WinRT 中的每个异步操作都需要实施此接口,该接口可提供执行异步操作所需的基本功能,查询其标识和状态,并请求其取消。但该特定接口缺少对异步操作来说无疑是至关重要的功能:当操作完成时,通过回调通知监听器。该功能有意地划分到了四个依赖IAsyncInfo 的其他接口中,而 WinRT 中的每个异步操作都需要实施以下四个接口之一:

public interface IAsyncAction : IAsyncInfo
{
    AsyncActionCompletedHandler Completed { get; set; }
    void GetResults();
}

public interface IAsyncOperation<TResult> : IAsyncInfo
{
    AsyncOperationCompletedHandler<TResult> Completed { get; set; }
    TResult GetResults();
}

public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
    AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
    AsyncActionProgressHandler<TProgress> Progress { get; set; }
    void GetResults();
}

public interface IAsyncOperationWithProgress<TResult, TProgress> : IAsyncInfo
{
    AsyncOperationWithProgressCompletedHandler<TResult, TProgress> Completed { get; set; }
    AsyncOperationProgressHandler<TResult, TProgress> Progress { get; set; }
    TResult GetResults();
}

这四个接口支持带有/不带结果,以及带有/不带进度报告的全部组合。所有这些接口都会公开一个 Completed 属性,该属性可以设置为在操作完成时调用的委派。您只能设置一次该委派,而如果该委派在操作完成后设置,它将通过处理操作完成和分配委派之间先后顺序的实施,立即加入计划或得到调用。

现在,我们假设要实施一个带有 XAML 按钮的 Metro 风格应用程序,单击该按钮可以将某些任务加入 WinRT 线程池,以执行某种大计算量的操作。当该任务完成时,按钮的内容将根据操作的结果进行更新。

我们应该如何实施此功能呢?WinRT ThreadPool 类公开了一种可以在池中异步运行任务的方法:

public static IAsyncAction RunAsync(WorkItemHandler handler);

我们可以使用此方法将大计算量的工作加入队列,以避免当在任务运行时阻止 UI 线程:

private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    int result = 0;
    var op = ThreadPool.RunAsync(delegate { result = Compute(); });
}

我们现在已经成功地将工作从 UI 线程分流到了池中,但我们如何获知工作何时完成呢?RunAsync 会返回一个 IAsyncAction,因此我们可以使用一个完成处理程序来接收该通知,并运行续��作为响应:

private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    int result = 0;
    var op = ThreadPool.RunAsync(delegate { result = Compute(); });
    op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
    {
        btnDoWork.Content = result.ToString(); // bug!
    };
}

现在,当加入 ThreadPool 队列的异步操作完成时,Completed 处理程序将得到调用,并尝试将结果存储到按钮中。很不幸,这一操作目前无法成功执行。Completed 处理程序不太可能在 UI 线程中得到调用,但是,为了修改btnDoWork.Content,该处理程序需要在 UI 线程中运行(如果无法实现,则将导致一个错误代码为“RPC_E_WRONG_THREAD”的异常)。为此,我们可以使用与 UI 相关的CoreDispatcher 对象将调用封送回所需的位置:

private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    int result = 0;
    var op = ThreadPool.RunAsync(delegate { result = Compute(); });
    op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
    {
        Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate
        {
            btnDoWork.Content = result.ToString();
        });
    };
}

现在,该应用可以正常工作了。但如果 Compute 方法抛出异常会怎样?如果某人调用返回自 ThreadPool.RunAsyncIAsyncAction 中的 Cancel 又会怎样?我们的 Completed 处理程序需要处理IAsyncAction 可能会以下列三种终端状态之一结束的事实:CompletedErrorCanceled

private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    int result = 0;
    var op = ThreadPool.RunAsync(delegate { result = Compute(); })
    op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
    {
        Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate
        {
            switch (asyncStatus)
            {
                case AsyncStatus.Completed:
                    btnDoWork.Content = result.ToString();
                    break;
                case AsyncStatus.Error:
                    btnDoWork.Content = asyncAction.ErrorCode.Message;
                    break;
                case AsyncStatus.Canceled:
                    btnDoWork.Content = "A task was canceled";
                    break;
            }
        });
    };
}

为了处理一个异步调用,我们就需要编写大量代码;如果我们需要连续执行多个异步操作,情形可想而知。如果我们能通过某种方法替代这种繁复的代码编写工作,岂不是非常美妙?

private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    try
    {
        int result = 0;
        await ThreadPool.RunAsync(delegate { result = Compute(); });
        btnDoWork.Content = result.ToString();
    }
    catch (Exception exc) { btnDoWork.Content = exc.Message; }
}

这段代码的功能与前一段代码完全相同。但我们不再需要手动处理完成回调。我们不再需要手动封送回 UI 线程。我们不再需要明确检查完成状态。并且我们不再需要颠倒控制流,这意味着我们可以轻而易举地扩展至更多操作,例如,循环执行多个计算和 UI 更新:

private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    try
    {
        for(int i=0; i<5; i++)
        {
            int result = 0;
            await ThreadPool.RunAsync(delegate { result = Compute(); });
            btnDoWork.Content = result.ToString();
        }
    }
    catch (Exception exc) { btnDoWork.Content = exc.Message; }
}

请回想一下手动使用 IAsyncAction 时,为实现上述功能必须编写的代码量。这就是 C# 和 Visual Basic 中新的async/await 关键字的魔力。好消息是您可以亲手编写这些代码,它们并非难以捉摸的魔法。在本博文余下的篇幅中,我们将带您深入了解这一功能的后台原理。

编译器转换

使用 async 关键字标记方法,会导致 C# 或 Visual Basic 编译器使用状态机重新编写该方法的实施。借助此状态机,编译器可以在该方法中插入多个中断点,以便该方法可以在不阻止线程的情况下,挂起和恢复其执行。这些中断点不会随意地插入。它们只会在您明确使用await 关键字的位置插入:

private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    ...
    await something; // <-- potential method suspension point
    ...
}

当您等待未完成的异步操作时,编译器生成的代码可确保与该方法相关的所有状态(例如,局部变量)封装并保留在堆中。然后,该函数将返回到调用程序,允许在其运行的线程中执行其他任务。当所等待的异步操作在稍后完成时,该方法将使用保留的状态恢复执行。

任何公开 await 模式的类型都可以进行等待。该模式主要由一个公开的 GetAwaiter 方法组成,该方法会返回一个提供IsCompletedOnCompletedGetResult 成员的类型。当您编写以下代码时:

await something;

编译器会生成一段代码,在实例 something 上使用这些成员来检查该对象是否已完成(通过 IsCompleted),如果未完成,则挂接一个续体(通过OnCompleted),并在该任务最终完成时进行回调以继续执行。该操作完成后,来自该操作的任何异常将得到传播或作为结果返回(通过GetResult)。因此,当您编写以下代码时:

private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
    ...
    await ThreadPool.RunAsync(delegate { result = Compute(); });
    ...
}

编译器会将其转换为类似如下的代码:

private class btnDoWork_ClickStateMachine : IAsyncStateMachine
{
    // Member fields for preserving locals and other necessary state
    int $state;
    TaskAwaiter<string> $awaiter;
    int result;
    ...
    // Method that moves to the next state in the state machine
    public void MoveNext()
    {
        // Jump table to get back to the right statement upon resumption
        switch (this.$state)
        {
            ...
            case 2: goto Label2;
            ...
        }
        ...
        // Expansion of await ...;
        var tmp1 = ThreadPool.RunAsync(delegate { this.result = Compute(); });
        this.$awaiter = tmp1.GetAwaiter();
        if (!this.$awaiter.IsCompleted)
        {
            this.$state = 2;
            this.$awaiter.OnCompleted(MoveNext);
            return;
            Label2:
        }
        this.$awaiter.GetResult();
        ...
    }
}

对于 btnDoWork_Click 方法,编译器会生成一个包含 MoveNext 方法的状态机类。对MoveNext 的每次调用都会恢复 btnDoWork_Click 方法的执行,直到遇到下一个针对某项未完成任务的 await 语句,或直到该方法结束,二者取其先。当编译器生成的代码发现未完成的所等待实例时,它会使用状态变量标记当前的位置,安排方法在所等待实例完成时继续执行,然后返回。当所等待实例最终完成时,系统将再次调用MoveNext 方法,并跳转至上次执行中断的位置。

编译器事实上并不关心 IAsyncAction 是否在此处等待。它只关心是否有可供绑定的正确模式。当然,您已经看到了 IAsyncAction 接口的实施,并且知道其中不包含编译器所期待的 GetAwaiter 方法。那么,为什么它能够成功进行编译并运行呢?为了更好地理解其中的原因,您需要首先了解 .NETTaskTask<TResult> 类型(该框架用于表示异步操作的核心),及其与 await 的关系。

转换为任务

.NET Framework 4.5 包含支持等待 TaskTask<TResult> 实例(Task<TResult> 派生自Task)所需的全部类型和方法。TaskTask<TResult> 均公开GetAwaiter 实例方法,并分别返回 TaskAwaiterTaskAwaiter<TResult> 类型,这两种类型均公开可满足 C# 和 Visual Basic 编译器需要的IsCompletedOnCompletedGetResult 成员。IsCompleted 会返回一个布尔值,指示在访问该属性的时刻,任务是否已经完成执行。OnCompleted 会将任务挂接到一个续体委派,该委派会在任务完成时得到调用(如果当OnCompleted 得到调用时该任务已经完成,该续体委派将加入异步执行计划)。GetResult 会在以TaskStatus.RanToCompletion 状态结束的情况下返回任务的结果(针对非泛型 Task 类型,返回 void);在以TaskStatus.Canceled 状态结束的情况下,抛出 OperationCanceledException;并在以TaskStatus.Faulted 状态结束的情况下,抛出导致任务失败的任何异常。

如果希望某种自定义类型支持等待,我们可以选择两种主要的方法。一种方法是针对自定义的可等待类型手动实施完整的 await 模式,提供一个返回自定义等待程序类型的GetAwaiter 方法,该等待程序类型知道如何处理续体和异常传播等等。第二种实施该功能的方法是将自定义类型转换为任务,然后只需依靠对等待任务的内置支持来等待特殊类型。我们来探究一下后一种方法。

.NET Framework 包含一种名为 TaskCompletionSource<TResult> 的类型,可以让此类转换更加直观。TaskCompletionSource<TResult> 会创建一个Task<TResult> 对象,并向您提供用于直接控制相应的任务将在何时、以何种状态结束的 SetResultSetExceptionSetCanceled 方法。因此,您可以将 TaskCompletionSource<TResult> 用作填充或代理来表示某些其他异步操作,例如,某种 WinRT 异步操作。

我们暂时假设您不知道自己可以直接等待 WinRT 操作。那么,您将如何来实现这些功能呢?

IAsyncOperation<string> op = SomeMethodAsync();
string result = await ...; // need something to await

您可以创建一个 TaskCompletionSource<TResult>,将其用作代理来表示该 WinRT 异步操作,然后等待相应的任务。我们来尝试一下。首先,我们需要实例化一个TaskCompletionSource<TResult>,以便等待其 Task

IAsyncOperation<string> op = SomeMethodAsync();
var tcs = new TaskCompletionSource<TResult>();
...
string result = await tcs.Task;

然后,如同我们在稍早前的示例中看到如何手动使用 WinRT 异步操作的 Completed 处理程序一样,我们需要向该异步操作挂接一个回调,以便获知其何时完成:

IAsyncOperation<string> op = SomeMethodAsync();

var tcs = new TaskCompletionSource<TResult>();

op.Completed = delegate { ... }; string result = await tcs.Task;

然后在该回调中,我们需要将 IAsyncOperation<TResult> 的完成状态传输给该任务:

IAsyncOperation<string> op = SomeMethodAsync();
var tcs = new TaskCompletionSource<TResult>();
op.Completed = delegate
{
    switch(operation.Status)
    {
        AsyncStatus.Completed:
            tcs.SetResult(operation.GetResults());
            break;
        AsyncStatus.Error:
            tcs.SetException(operation.ErrorCode);
            break;
        AsyncStatus.Canceled:
            tcs.SetCanceled();
            break;
    }
};
string result = await tcs.Task;

就是这样。即使处理程序在该操作已经完成后才注册,WinRT 异步操作也可确保 Completed 得到适当的调用,因此我们在注册该处理程序时,无需针对其与该操作完成的先后顺序进行特殊处理。WinRT 异步操作还负责在操作完成后将引用下降至Completed 处理程序,因此我们无需进行任何特殊处理,以便在处理程序得到调用时将 Completed 设置为 null;事实上,Completed 处理程序只能设置一次,这意味着如果您再次尝试设置它,将引发一个错误。

借助此方法,WinRT 异步操作的完成状态和代表任务的完成状态之间将建立起一对一的映射:

终端 AsyncStatus

转换为 TaskStatus

等待内容

Completed

RanToCompletion

返回操作的结果(或 void)

Error

Faulted

抛出失败操作的异常

Canceled

Canceled

抛出 OperationCanceledException

当然,如果我们在每次希望等待某个 WinRT 异步操作时都必须编写这段用于处理此 await 操作的样板代码,代码很快将变得冗长而乏味。作为优秀的程序开发人员,我们可以将该样板封装到一个方法中,以便反复使用。我们可以编写一个将 WinRT 异步操作转换为任务的扩展方法:

public static Task<TResult> AsTask<TResult>(
    this IAsyncOperation<TResult> operation)
{
    var tcs = new TaskCompletionSource<TResult>();
    operation.Completed = delegate     {
        switch(operation.Status)
        {
            AsyncStatus.Completed:
                tcs.SetResult(operation.GetResults());
                break;
            AsyncStatus.Error:
                tcs.SetException(operation.ErrorCode);
                break;
            AsyncStatus.Canceled:
                tcs.SetCanceled();
                break;
        }
    };
    return tcs.Task;
}

借助该扩展方法,我现在可以编写如下的代码:

IAsyncOperation<string> op = SomeMethodAsync();
string result = await op.AsTask();

甚至可以简化为:

string result = await SomeMethodAsync().AsTask();

这样就好多了。当然,此类可重用的 AsTask 功能对于在 C# 和 Visual Basic 中使用 WinRT 的任何人来说都必不可少,因此您实际上不需要编写自己的实施:.NET 4.5 中已经内置了此类方法。System.Runtime.WindowsRuntime.dll 程序集包含了用于 WinRT 异步接口的这些扩展方法:

namespace System
{
    public static class WindowsRuntimeSystemExtensions
    {
        // IAsyncAction

        public static Task AsTask(
            this IAsyncAction source);
        public static Task AsTask(
            this IAsyncAction source, 
            CancellationToken cancellationToken);

        // IAsyncActionWithProgress

        public static Task AsTask<TProgress>(
            this IAsyncActionWithProgress<TProgress> source);
        public static Task AsTask<TProgress>(
            this IAsyncActionWithProgress<TProgress> source, 
            IProgress<TProgress> progress);
        public static Task AsTask<TProgress>(
            this IAsyncActionWithProgress<TProgress> source, 
            CancellationToken cancellationToken);
        public static Task AsTask<TProgress>(
            this IAsyncActionWithProgress<TProgress> source, 
            CancellationToken cancellationToken, 
            IProgress<TProgress> progress);

        // IAsyncOperation

        public static Task<TResult> AsTask<TResult>(
            this IAsyncOperation<TResult> source);
        public static Task<TResult> AsTask<TResult>(
            this IAsyncOperation<TResult> source, 
            CancellationToken cancellationToken);

        // IAsyncOperationWithProgress

        public static Task<TResult> AsTask<TResult, TProgress>(
            this IAsyncOperationWithProgress<TResult, TProgress> source);
        public static Task<TResult> AsTask<TResult, TProgress>(
            this IAsyncOperationWithProgress<TResult, TProgress> source, 
            IProgress<TProgress> progress);
        public static Task<TResult> AsTask<TResult, TProgress>(
            this IAsyncOperationWithProgress<TResult, TProgress> source, 
            CancellationToken cancellationToken);
        public static Task<TResult> AsTask<TResult, TProgress>(
            this IAsyncOperationWithProgress<TResult, TProgress> source, 
            CancellationToken cancellationToken, 
            IProgress<TProgress> progress);

        ...
    }
}

这四种接口均具有一个无参数的 AsTask 重载,与我们刚刚从头编写那个非常类似。此外,每种接口还具有一个接受 CancellationToken 的重载。此标记是 .NET 中用于提供可组合和可合作取消的常见机制;您会向所有异步操作传入一个标记,而当系统请求取消时,所有这些异步操作都将接到取消请求。您现在已经知道存在现成可用的此类 API,但为了说明其原理,我们还是要向您介绍一下如何构建此类 AsTask(CancellationToken) 重载。CancellationToken 提供了一个Register 方法,该方法会接受将在请求取消时调用的委派;我们可以简单地提供一个委派,然后通过取消请求调用 IAsyncInfo 对象中的 Cancel,并通过取消请求转发:

public static Task<TResult> AsTask<TResult>(
    this IAsyncOperation<TResult> operation, 
    CancellationToken cancellationToken
{
    using(cancellationToken.Register(() => operation.Cancel()))
        return await operation.AsTask();
}

尽管 .NET 4.5 中自带的实施并非与此完全相同,但逻辑上基本一致。

对于 IAsyncActionWithProgress<TProgress>IAsyncOperationWithProgress<TResult,TProgress>,还具有接受IProgress<TProgress> 的重载。IProgress<T> 是一种可以由方法接受以通报回进度的 .NET 接口,而AsTask 方法只需为 WinRT 异步操作的 Progress 属性连接委派,即可将进度信息转发给IProgress。同样,我们将展示手动实施该功能的方法,以供您进行参考:

public static Task<TResult> AsTask<TResult,TProgress>(
    this IAsyncOperationWithProgress<TResult> operation,
    IProgress<TProgress> progress
{
    operation.Progress += (_,p) => progress.Report(p);
    return operation.AsTask();
}

直接等待 WinRT 异步操作

我们现在已经知道了如何创建代表 WinRT 异步操作的任务,以便可以对其进行等待。但是,如何才能直接等待 WinRT 操作呢?换言之,最好能够编写如下的代码:

await SomeMethodAsync().AsTask();

但对于不需要提供 CancellationTokenIProgress<T> 的情况,避免编写调用AsTask 的代码岂不更好?

await SomeMethodAsync();

当然,我们已在本博文的开头提到,这是可以实现的。请回忆一下编译器如何期待发现一个返回适当等待程序类型的 GetAwaiter 方法。如前所述,System.Runtime.WindowsRuntime.dll 中的WindowsRuntimeSystemExtensions 类型包含针对四种 WinRT 异步接口的此类 GetAwaiter 扩展方法:

namespace System
{
    public static class WindowsRuntimeSystemExtensions
    {
        ...
        public static TaskAwaiter GetAwaiter(
            this IAsyncAction source);
        public static TaskAwaiter<TResult> GetAwaiter<TResult>(
            this IAsyncOperation<TResult> source);
        public static TaskAwaiter GetAwaiter<TProgress>(
            this IAsyncActionWithProgress<TProgress> source);
        public static TaskAwaiter<TResult> GetAwaiter<TResult, TProgress>(
            this IAsyncOperationWithProgress<TResult, TProgress> source);
    }
}

请注意来自这些方法的返回类型:TaskAwaiterTaskAwaiter<TResult>。这些方法均利用了框架内置的现有任务等待程序。根据已经掌握的AsTask 相关知识,您可能已经猜到了这些功能的实施方法。框架中的实际实施基本上与此完全相同:

public static TaskAwaiter GetAwaiter(
    this IAsyncAction source)
{
    return source.AsTask().GetAwaiter();
}

这意味着这两行代码将引发完全相同的行为:

await SomeMethodAsync().AsTask();
await SomeMethodAsync();

自定义等待行为

如前所述,TaskAwaiterTaskAwaiter<TResult> 可提供满足编译器对等待程序的期待所需的全部成员:

bool IsCompleted { get; }
void OnCompleted(Action continuation);
TResult GetResult(); //returns void on TaskAwaiter

其中最有趣的成员为 OnCompleted,它将在所等待的操作完成时,负责调用续体委派。OnCompleted 提供特殊封送行为,以确保续体委派在正确的位置执行。

在默认情况下,当任务等待程序的 OnCompleted 得到调用时,会通知当前的 SynchronizationContext,SynchronizationContext 是代码执行环境的抽象表示。在 Metro 风格应用程序的 UI 线程中,SynchronizationContext.Current 会返回一个内部WinRTSynchronizationContext 类型的实例。SynchronizationContext 会提供一个虚拟的Post 方法,该方法会接受一个委派,并在上下文的适当位置执行该委派;WinRTSynchronizationContext 封装了一个CoreDispatcher,并使用其 RunAsync 将委派异步调用回 UI 线程(我们稍早前已在本博文中手动实施了该功能)。当所等待的任务完成时,委派将作为Post 传递给 OnCompleted,以便在调用 OnCompleted 时捕获的当前SynchronizationContext 中执行。正是该机制允许您在 UI 逻辑中使用 await 编写代码,而无需担心是否能封送回正确的线程:任务的等待程序将为您处理妥当。

当然,在某些情况下,您可能不希望执行这种默认的封送行为。此类情况多在库中出现:许多类型的库不关心操作 UI 控件及运行其自身的线程,因此从性能的角度来考虑,避免与跨线程封送相关的开销将不无裨益。为了适应希望禁用这种默认封送行为的代码,TaskTask<TResult> 提供了 ConfigureAwait 方法。ConfigureAwait 接受布尔值continueOnCapturedContext 参数:传递 True 表示使用默认行为,传递 False 表示系统不需要将委派的调用强制封送回原始上下文,而是在系统认为适当的任意位置执行该委派。

因此,如果您希望在不强迫剩余的代码返回 UI 线程执行的情况下等待某个 WinRT 操作,请编写以下任一代码作为替换:

await SomeMethodAsync();

或者:

await SomeMethodAsync().AsTask();

您可以编写:

await SomeMethodAsync().AsTask()
                      .ConfigureAwait(continueOnCapturedContext:false);

或仅编写:

await SomeMethodAsync().AsTask().ConfigureAwait(false);

何时使用 AsTask

如果您只是希望调用某个 WinRT 异步操作并等待其完成,直接等待该 WinRT 异步操作是最为简单和清洁的方法:

await SomeMethodAsync();

但是如果您希望获得更强的控制力,就需要使用 AsTask。我们已经介绍了 AsTask 的一些用途:

  • 通过 CancellationToken 支持取消

    CancellationToken token = ...; await SomeMethodAsync().AsTask(token);

  • 通过 IProgress<T> 支持进度报告
    IProgress<TProgress> progress = ...;
    await SomeMethodAsync().AsTask(progress);
  • 通过 ConfigureAwait 取消默认续体封送行为
    await SomeMethodAsync().AsTask().ConfigureAwait(false);

在许多其他重要的情况下,AsTask 也能发挥很大的作用。

其中之一与 Task 支持多续体的功能有关。WinRT 异步操作类型仅支持注册了 Completed 的单个委派(Completed 是一个属性而非事件),并且该委派只能设置一次。在大多数情况下,这没什么影响,因为您只需要等待该操作一次,例如,作为调用某个异步方法的替代方案:

SomeMethod();

您可以调用并等待某个异步对应体:

await SomeMethodAsync();

逻辑上,这将保持与使用异步对应体相同的控制流。但有时,您希望能够挂接多个回调,或者您希望能够多次等待同一个实例。与 WinRT 异步接口相反,Task 类型支持任意次等待和/或任意次使用其ContinueWith 方法以支持任意次回调。因此,您可以使用 AsTask 来为您的 WinRT 异步操作获取任务,然后将多个回调挂接到Task 而不是直接挂接到 WinRT 异步操作。

var t = SomeMethodAsync().AsTask();
t.ContinueWith(delegate { ... });
t.ContinueWith(delegate { ... });
t.ContinueWith(delegate { ... });

AsTask 的另一个用途是处理通过 TaskTask<TResult> 类型运行的方法。Task.WhenAllTask.WhenAny 等组合方法会通过 Task,而非 WinRT 异步接口运行。因此,如果您希望能够调用多个 WinRT 异步操作,然后await 他们全部或部分完成,您可以使用 AsTask 来简化这一过程。例如,本 await 会在所提供的三个操作中的任意一个完成时完成,并返回代表其内容的 Task

Task firstCompleted = await Task.WhenAny(
    SomeMethod1Async().AsTask(),
    SomeMethod2Async().AsTask(),
    SomeMethod3Async().AsTask());

结论

了解到 WinRT 通过异步操作提供了如此众多的功能的确令人倍感兴奋;大量公开的此类 API 证明了响应速度对于该平台的重要性。这也意味着开发人员急切渴求用于处理这些操作的编程模型:对于 C# 和 Visual Basic 代码,awaitAsTask 可谓雪中送炭。希望本博文提供的技术内幕能够帮助您更好地理解这些功能的具体工作原理,并帮助您高效地开发 Metro 风格应用程序。

你可能感兴趣的:(object,basic,任务,interface,编译器)