使用 Windows 运行时中异步性来始终保持应用程序能够快速流畅地运行

转自:http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/03/26/windows.aspx

人类的思维方式在本质上不是同步的,这直接影响着我们对应用程序响应方式的预期。在构建快速流畅的 Metro 风格应用程序的过程中,Windows 运行时 (WinRT) 将这种异步性视为头等大事。如果您要构建 Metro 风格的应用程序,有时需要编写一些异步代码。在本博文中,我们将讨论异步编程在 WinRT 中得以普遍采用的原因所在,还将介绍在应用程序中使用异步编程方式的基础操作,以及这种编程方式的工作原理方面的背景知识。

快速和流畅的应用程序必须具有极强的响应能力。

使用 Windows 应用程序时程序突然停止响应、屏幕变灰且圆环转个不停的情况对于您来说是否已经司空见惯?通常您需要苦苦等待很久才能继续操作。更糟的是,您所付出的大量心血很可能会因此而付之东流。

用户希望在所有交互过程中应用程序都可以做出快速的响应。当使用自己最喜欢的阅读应用程序时,他们通常要执行添加新闻源、阅读新闻报道和保存相关内容等操作。即使当应用程序从 Internet 获取数据时,他们也应当可以正常执行这些操作。

当用户使用触控功能与应用程序进行交互时,这一点就显得尤为重要。用户注意到当应用程序无法达到“粘住手指”的响应速度时,即使出现小的性能问题,用户体验也将大打折扣,同时应用程序快速流畅的感觉也将被破坏。

当停止响应用户输入时,应用程序的快速流畅感也将无法保证。那么,为何应用程序会停止响应呢?其中的一个重要原因在于应用程序是同步的,它需要等待其他任务(如从 Internet 获取数据)完成之后才能继续响应您的输入。

许多现代应用程序都需要执行连接到社交网站、进行云存储、在硬盘上处理文件以及与其他小工具和设备进行通信等任务。如果构建方式错误,应用程序会将大量的时间花费等待外部环境上,从而无法保证足够的时间来响应用户的需求。

因此,解决这种连接环境问题成为了我们着手设计 Windows 运行时 (WinRT) 的 API 时的核心原则。我们觉得提供一个强大的 API 表面来默认使应用程序能够快速流畅地运行的重要性不言而喻。

为实现这些目标,我们对 Windows Runtime 中许多可能受输入/输出限制的 API 进行了异步化处理。如果编写同步代码,则这些 API 对性能的影响将变得显而易见(例如,执行时间超过 50 毫秒)。这种异步化 API 的方法不仅允许您编写可默认实现快速流畅风格的代码,而且还提升了 Metro 风格应用程序开发中应用程序响应速度的重要性。

如果您对异步模式并不熟悉,可将 WinRT 中的异步性看作是通过电话为某人提供回拨号码。首先,为他/她提供回拨号码,接下来挂断,然后继续执行其他相关操作。当准备就绪且可以通话时,他/她可以使用您提供的号码进行回拨。这是在 WinRT 中实现异步性的关键所在。

我们将首先看一下如何以一种直观的方式来使用这些异步 API,以便进一步了解 WinRT 中异步性的更多相关信息。随后,我们将深入了解 WinRT 中引入的异步基元(构建新语言功能的基础)和它们的工作原理。

使用方法:面向开发人员的全新异步增强功能

在过去,编写异步代码往往困难重重。就解决这一问题,我们负责生成 Windows 8 Metro 风格应用程序工具的团队在最新版本中取得了重大突破。在 .NET Framework 4.0 中,我们引入了任务并行类库。随后,C# 和 Visual Basic 中引入了 await 关键字。C++ 在技术方面,例如并行模式类库 (PPL),进行了大量的工作。JavaScript 具有诸如 Promises 等强大的工具。

其中每一种技术都为您提供了编写异步代码的直观方法。通过在内部引入这些技术使用的标准异步模式,您可以在构建 Metro 风格的应用程序时使用这些技术,无论您使用何种编程语言。

就您最喜欢的新闻应用程序而言,通过使用 Windows.Web.Syndication.SyndicationClient API,即使是在接收新闻报道时,代码依然能保持较快的响应速度。对 RetrieveFeedAsync 的调用可以立即返回。这非常重要,因为从 Internet 返回结果的时间通常很长。同时,UI 将继续与用户进行交互。

标黄的代码将在调用 RetrieveFeedAsync 之后运行。您无需考虑“恢复中断进程”联结。代码可以与编写同步代码的相同的方式直接编写。

异步检索 RSS 源的 JavaScript 代码:

var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
    title = feed.title.text;
});

异步检索 RSS 源的 C# 代码:

var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
var feed = await client.RetrieveFeedAsync(feedUri);
var title = feed.Title.Text;

异步检索 RSS 源的 VB 代码:

Dim feedUri = New Uri("http://www.devhawk.com/rss.xml");
Dim client = New Windows.Web.Syndication.SyndicationClient();
Dim feed  = Await client.RetrieveFeedAsync(feedUri);
Dim title = feed.Title.Text;

异步检索 RSS 源的 C++ 代码:

auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
    this->title = feed->Title->Text;
});

这些代码中未提及线程、上下文切换、调度程序等内容。用于编写异步代码的全新开发人员增强功能将承担起相应的工作。异步 API 调用之后的代码将在与原始调用相同的上下文中进行回拨。这意味着您可以进一步使用结果更新 UI,而无需担心返回到 UI 线程。

使用方法:错误处理

当然���API 调用也可能失败(例如,在检索 RSS 源时网络断开)。要做到真正的强大,我们需要具备可应对这些错误的弹性机制。使用这些语言中的异步功能将使错误处理变得更为直观。在不同的语言中,这种机制也会有所不同。

对于 JavaScript,我们建议您始终使用 done 来结束 Promise 链。这样可以确保开发人员可以看到 Promise 链中捕获到的任何异常(例如,通过在错误处理程序中报告或抛出异常)。Done 的签名与 then 相同。因此,要处理错误,您只需将错误代理传送到 done(): 即可

var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
    title = feed.title.text;
}, function(error) {
    console.log('an error occurred: ');
    console.dir(error);
});

要在 C# 或 Visual Basic 中处理异常,您需要使用 try/catch block 语句,具体方法与处理今天的同步代码相同:

var title;
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

try
{
    var feed = await client.RetrieveFeedAsync(feedUri);
    title = feed.Title.Text;
}
catch (Exception ex)
{
    // An exception occurred from the async operation
}

在 C++ 中处理异步错误的最常用方法就是使用抛出异常的其他基于任务的续体(您可以在开发中心的 C++ 中的异步编程中找到有关 C++ 中错误处理的其他方法):

auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
    this->title = feed->Title->Text;
}).then([] (task<void> t) {
    try
    {
        t.get();
    // .get() didn't throw, so we succeeded
    }
    catch (Exception^ ex)
    {
        // An error occurred
    }
});

有关如何使用这些异步改进的详细信息(包括支持取消和进度等更多情景),请参阅:
•Windows 开发中心的 Windows 运行时中的异步编程
•Windows 开发中心的 JavaScript 中的异步编程
•Windows 开发中心的 C++ 中的异步编程
•Windows 开发中心的 .NET 中的异步编程
•Windows 开发中心的使用 JavaScript 中的 Promises 时如何处理错误

工作原理:WinRT 异步基元

异步方法非常强大,但您可能会对其中的一些工作原理不甚了解。要说明它的工作原理,让我来进一步了解一下异步方法在 WinRT 中的工作方式。我们首先需要调查构建模型所依托的基元。大多数人可能根本不会用到这些基元(如果发现必须使用的情景,我们愿意洗耳恭听此类反馈)。

要从 C# 代码开始,我们需要了解一下 RetrieveFeedAsync 的实际返回类型(C# Intellisense 中显示的内容):

这里写图片描述
图 1. Intellisense 针对 RetrieveFeedAsync 方法显示的内容

RetrieveFeedAsync 返回 IAsyncOperationWithProgress 接口。IAsyncOperationWithProgress是定义 WinRT 的核心异步编程外观的 5 种接口之一:IAsyncInfo、IAsyncAction、IAsyncActionWithProgress、IAsyncOperation 和 IAsyncOperationWithProgress。

WinRT 异步模型的核心接口依托于 IAsyncInfo 而构建。该核心接口可以定义异步操作(例如,当前状态、取消操作的功能和失败操作的错误等)的属性。

如上文所述,异步操作可以返回结果。WinRT 中的异步操作还可以在运行时报告进度。上文中的其他 4 种异步接口(IAsyncAction、IAsyncActionWithProgress、IAsyncOperation 和 IAsyncOperationWithProgress)可以定义不同的结果组合和进度。

使用 Windows 运行时中异步性来始终保持应用程序能够快速流畅地运行_第1张图片
图 2. 不同的结果组合和进度

提供 Windows 运行时中的核心基元,并允许 C#、Visual Basic、C++ 和 JavaScript 以您熟悉的方式计划 WinRT 异步操作。

请注意:从冷启动迁移到热启动

在 //build/ 大会上发布的 Windows 8 Developer Preview 中,所有 WinRT 中的异步操作都是冷启动。这些操作不会立即运行。它们必须由开发人员专门启动,或在使用 C#、Visual Basic、JavaScript 或 C++ 中的异步功能时隐式启动。我们从不同来源得到反馈显示这种方法并不直观,并且有可能使用户感到痛苦和困惑。

在 Windows 8 Consumer Preview 版本中,您会注意到我们已从 WinRT 异步基元中删除了 Start() 方法。现在,我们的异步操作均使用热启动。因此,异步方法会在将操作返回到调用程序之前启动操作。

除这一更改外,Consumer Preview 中还进行了许多其他的更改,我们根据来自用户的所有重要反馈调整了开发人员平台。

工作原理:结果、取消和异步基元错误

异步操作在 Started 状态下启动,并且可进入其他三种状态之一:Canceled、Completed 和 Error。异步操作的当前状态可在异步操作的 Status 属性中得以体现(以 AsyncStatus 枚举类型表示)。

我们处理异步操作的第一步是连接 Completed 处理程序。在 Completed 处理程序中,我们可以获取结果并使用它们。

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
            op = client.RetrieveFeedAsync(feedUri);

            op.Completed = (info, status) =>
            {
                SyndicationFeed feed = info.GetResults();
                UpdateAppWithFeed(feed);
            };

有时您可能需要取消操作。您可以通过在异步操作时调用 Cancel 方法来完成该操作。

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

Completed 处理程序将始终用于调用异步操作,无论其状态是 Completed、Canceled 还是 Error。仅在异步操作的状态为 Completed 时调用 GetResults(例如,在操作状态为 Canceled 或 Error 时不调用)。您可以通过查看状态参数来确定状态。

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
            op = client.RetrieveFeedAsync(feedUri);
            op.Cancel();

            op.Completed = (info, status) =>
            {
                if (status == AsyncStatus.Completed)
                {
                    SyndicationFeed feed = info.GetResults();
                    UpdateAppWithFeed(feed);
                }
                else if (status == AsyncStatus.Canceled)
                {
                    // Operation canceled
                }
            };

如果操作失败,则代码依然不够强大。如同取消一样,相同的异步操作也支持错误报告。另外,还可以使用 AsyncStatus 来区分 Completed 和 Canceled 状态,并确定异步操作是否失败(例如,AsyncStatus.Error)。您可以在异步操作的 ErrorCode 属性中找到特定的失败代码。

op.Completed = (info, status) =>
            {
                if (status == AsyncStatus.Completed)
                {
                    SyndicationFeed feed = info.GetResults();
                    UpdateAppWithFeed(feed);
                }
                else if (status == AsyncStatus.Canceled)
                {
                    // Operation canceled
                }
                else if (status == AsyncStatus.Error)
                {
                    // Error occurred, Report error
                }
            };

工作原理:使用异步基元报告进度

某些 WinRT 异步操作可以在运行时提供进度通知。您可以使用这些通知向用户报告异步操作的当前进度。

在 WinRT 中,异步报告是通过 IAsyncActionWithProgress 和 IAsyncOperationWithProgress

op = client.RetrieveFeedAsync(feedUri);

            float percentComplete = 0;
            op.Progress = (info, progress) =>
            {
                percentComplete = progress.BytesRetrieved / 
(float)progress.TotalBytesToRetrieve;
            };

在此实例中,第二个参数 (progress) 的类型是 RetrievalProgress。该类型包含两个参数 (bytesRetrieved 和 totalBytesToRetrieve),可用于报告我们的当前进度。

有关更为深入的内容(涉及 WinRT 中的异步编程或常规异步),请查看:
•//Build 大会的“异步无处不在”讨论
•在 JavaScript 中使用“Promises”进行异步编程
•基于任务的异步模式
•.NET 中的异步性

结束语

WinRT 中普遍使用异步操作的有趣之处在于其可以影响您构建代码和应用程序的方式。您不禁会认为您的应用程序是以一种更为松散的方式连接的。如果您有此方面的任何反馈,我们将非常乐意倾听!请通过在此处或开发人员论坛中留言来联系我们。

我们希望您的客户喜欢您的 Windows 8 应用程序。我们希望这些应用程序可以处于活动状态并保持这种状态。另外,我们希望使您创建这些应用程序的过程变得更为轻松。我们希望您能够轻松连接到社交网站、进行云存储、在硬盘上处理文件以及与其他小工具和设备进行通信,同时为您的客户提供另人惊叹的用户体验。

–Jason Olson

Windows 项目经理

你可能感兴趣的:(异步,wp,await)