向客户端代码公开异步功能:Windows Phone 7





5.00/5 (4投票s)
如何在 Windows Phone 7 上使用实现 IAsyncResult 接口的类型。
引言
如今,具备互联网和多媒体功能的智能手机已变得非常流行。幸运的是,如果您是一名 .NET 开发人员,您可以使用现有的技能和语言来定位一些最受欢迎的移动操作系统。
Windows Phone 7 是微软开发的移动操作系统,也是其 Windows Mobile 平台的后续产品。
背景
您可以使用以下 .NET Framework 异步编程模型之一将异步功能暴露给客户端代码:
IAsyncResult
CLR 的异步编程模型 (APM)- 基于事件的异步模式 (EAP)
IAsyncResult
自 .NET 1.0 起就一直存在,并被大多数 BCL 类使用,而 EAP 的主要优点是它与 Visual Studio UI 设计器集成。您可以通过这篇 MSDN 上的精彩文章学习如何正确实现 IAsyncResult
接口 (APM):Jeffrey Richter 撰写的 实现 CLR 异步编程模型。
在这篇文章中,我将向您展示在 Windows Phone 7 上使用实现 IAsyncResult
接口的类型是多么容易。我将使用 PowerThreading 库[1],因为它提供了一个与原始 MSDN 文章中所述的类似(甚至可以说改进了)的实现。我将解释如何使用它,以及使用 AsyncEnumerator
类如何让这一切变得更容易。
问题
上面链接的代码用于演示。此代码执行一些 I/O 操作,这会导致线程阻塞。
public interface IWebService
{
IStockQuote FetchStockQuotes();
}
internal sealed class WebService : IWebService
{
private readonly IStockQuote m_quotes;
/// <summary>
/// Initializes a new instance of the <see cref="WebService"/> class.
/// </summary>
/// <param name="quotes">The quotes.</param>
public WebService(IStockQuote quotes)
{
m_quotes = quotes;
}
#region IWebService Members
/// <summary>
/// Gets the stock quotes.
/// </summary>
/// <returns></returns>
public IStockQuote FetchStockQuotes()
{
Thread.Sleep(50); // Simulate time-consuming task.
return m_quotes;
}
#endregion
}
同步 I/O 会暂停 UI
下面的代码显示了 ExecuteWithSyncIO
方法的实现。该应用程序向用户显示一个 MessageBox
,告知用户在执行过程中 UI 将暂停。
private void ExecuteWithSyncIO()
{
for (Int32 n = 0; n < c_iterations; n++)
{
m_webService.GetStockQuotes();
}
SetStatus("Sync/IO completed.", StatusState.Ready);
}
代理的 BeginInvoke 方法不受支持
下面的代码显示了 ExecuteWithDelegateBeginInvoke
方法的实现。
此方法仅用于演示,因为在 .NET Compact Framework 中不允许异步调用代理。
private void ExecuteWithDelegateBeginInvoke()
{
Func<IStockQuote> stockQuoteDelegate = m_webService.GetStockQuotes;
// NOTE: Calling delegates asynchronously is NOT supported in WP7.
stockQuoteDelegate.BeginInvoke((ar) => {
stockQuoteDelegate.EndInvoke(ar);
}, null);
}
解决方案
Wintellect.Threading.AsyncProgModel.AsyncResult<TResult>
类包含 IAsyncResult
接口的实现。该类型是泛型的,我们可以轻松地将其用于 WebService
类。
using System;
using System.Threading;
using Wintellect.Threading.AsyncProgModel;
public interface IWebService
{
IStockQuote FetchStockQuotes();
}
internal sealed class WebService : IWebService
{
private readonly IStockQuote m_quotes;
/// <summary>
/// Initializes a new instance of the <see cref="WebService"/> class.
/// </summary>
/// <param name="quotes">The quotes.</param>
public WebService(IStockQuote quotes)
{
m_quotes = quotes;
}
// Asynchronous version of time-consuming method (Begin part).
public IAsyncResult BeginGetStockQuotes(AsyncCallback callback, Object state)
{
// Create IAsyncResult Object identifying the
// asynchronous operation.
AsyncResult<IStockQuote> ar =
new AsyncResult<IStockQuote>(callback, state);
// Use a thread pool thread to perform the operation.
ThreadPool.QueueUserWorkItem(GetStockQuotesHelper, ar);
return ar; // Return the IAsyncResult to the caller.
}
// Asynchronous version of time-consuming method (End part).
public IStockQuote EndGetStockQuotes(IAsyncResult asyncResult)
{
// We know that the IAsyncResult is really an
// AsyncResult<IStockQuote> object.
AsyncResult<IStockQuote> ar =
(AsyncResult<IStockQuote>)asyncResult;
// Wait for operation to complete, then return result or
// throw exception.
return ar.EndInvoke();
}
private void GetStockQuotesHelper(Object state)
{
// We know that it's really an AsyncResult<IStockQuote> object.
AsyncResult<IStockQuote> ar = (AsyncResult<IStockQuote>)state;
try
{
// Perform the operation; if sucessful set the result.
IStockQuote quotes = FetchStockQuotes();
ar.SetAsCompleted(quotes, false);
}
catch (Exception e)
{
// If operation fails, set the exception.
ar.SetAsCompleted(e, false);
}
}
#region IWebService Members
/// <summary>
/// Gets the stock quotes.
/// </summary>
/// <returns></returns>
public IStockQuote FetchStockQuotes()
{
Thread.Sleep(5); // Simulate time-consuming task.
return m_quotes;
}
#endregion
}
实际上,GetStockQuotesHelper
方法可以内联。我尽量避免内联代理,因为您可以轻松访问父方法体中定义的变量。
现在让我们看看以上内容如何在 Windows Phone 7 上使用。
IAsyncResult 接口
下面的代码显示了 ExecuteWithIAsyncResult
方法的实现。唯一的问题是,在使用 IAsyncResult
时,您需要指定一个方法,在相应的异步操作完成时调用。这可能会导致使用同步构造来避免竞态条件。它还会分割您的代码流程。您可以使用 匿名方法或 Lambda 表达式内联回调方法,如下所示,但如果您的逻辑很复杂,您的代码将不好看。
private void ExecuteWithIAsyncResult()
{
SetStatus("Working..", StatusState.Busy);
for (Int32 n = 0; n < c_iterations; n++)
{
m_webService.BeginGetStockQuotes((ar) => {
// Callback method inlined using Lamda Expressions.
// NOTE: Code can become ugly here, specially if you need to do
// a lot of stuff that touch properties bounded with UI elements.
if (Interlocked.Increment(ref m_numDone) == c_iterations)
{
Execute.OnUIThread(() => {
SetStatus("IAsyncResult APM completed.",
StatusState.Ready);
});
}
}, null);
}
}
AsyncEnumerator 类
下面的代码显示了 ExecuteWithAsyncEnumerator
方法的实现。如您所见,此方法使您的代码看起来像是在同步执行,但实际上它是异步执行的。您不必将代码分割成回调方法或内联代理。您不需要使用 Dispatcher
或 SynchronizationContext
来编组对 UI 线程的调用。所有这些都由 AsyncEnumerator
类处理。
private IEnumerator<Int32> ExecuteWithAsyncEnumerator(AsyncEnumerator ae)
{
for (Int32 n = 0; n < c_iterations; n++)
{
m_webService.BeginGetStockQuotes(ae.End(), null);
}
// AsyncEnumerator captures the calling thread's SynchronizationContext.
// Set the Wintellect.Threading.AsyncProgModel.SynchronizationContext to
// null so that the callback continues on a ThreadPool thread.
ae.SyncContext = null;
yield return c_iterations;
for (Int32 n = 0; n < c_iterations; n++)
{
m_webService.EndGetStockQuotes(ae.DequeueAsyncResult());
}
// AsyncEnumerator captures the synchronization context.
SetStatus("AsyncEnumerator completed.", StatusState.Ready);
}
关注点
虽然我在本文中讨论的内容适用于 移动应用程序开发,但同样的原则也可以应用于 富 Internet 应用程序和 智能客户端。我使用 AsyncEnumerator
类已经两年多了,我不得不说它改变了我对使用 APM 的看法。最终,交付响应迅速的应用程序能让最终用户满意。
更多示例可以在我的 GitHub 仓库中找到。.
参考文献
- AsyncEnumerator 类位于 PowerThreading 库中。它由 Jeffrey Richter 编写,可以从 Wintellect 网站获得。