65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2011年1月15日

CPOL

3分钟阅读

viewsIcon

43847

downloadIcon

655

如何在 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 方法的实现。如您所见,此方法使您的代码看起来像是在同步执行,但实际上它是异步执行的。您不必将代码分割成回调方法或内联代理。您不需要使用 DispatcherSynchronizationContext 来编组对 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 仓库中找到。.

参考文献

  1. AsyncEnumerator 类位于 PowerThreading 库中。它由 Jeffrey Richter 编写,可以从 Wintellect 网站获得。
© . All rights reserved.