如何围绕异步方法编写包装器?

时间:2011-09-20 23:56:41

标签: c# events windows-phone-7 asynchronous webclient

我有一个JSON API,我希望我的应用程序可以访问。所以我写了一个方法。

public List<Books> GetBooks()
{
  var webclient = new WebClient();
  var jsonOutput = webclient.DownloadString(
                         new Uri("http://someplace.com/books.json")
                             );

  return ParseJSON(jsonOutput);//Some synchronous parsing method 
}

现在我需要将DonwloadString更改为DownloadStringAsync。 我找到了tutorial

但这似乎太复杂了。我试图让这个工作,但我不确定这是否是正确的方法。也许有一种更简单,更好的方式?

2 个答案:

答案 0 :(得分:7)

要求您订阅事件以获取结果的所有异步操作都很痛苦。我认为最简单的方法是将事件处理抽象为一些很好的扩展方法,并使用连续传递样式(CPS)来处理结果。

所以,首先要创建一个下载字符串的扩展方法:

public static void DownloadString(this Uri uri, Action<string> action)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = new WebClient();

    DownloadStringCompletedEventHandler handler = null;
    handler = (s, e) =>
    {
        var result = e.Result;
        webclient.DownloadStringCompleted -= handler;
        webclient.Dispose();
        action(result);
    };

    webclient.DownloadStringCompleted += handler;
    webclient.DownloadStringAsync(uri);
}

此方法隐藏了WebClient的创建,所有事件处理,以及处理和取消订阅以便事后清理。

它的使用方式如下:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t =>
{
    // Do something with the string
});

现在,这可用于创建GetBooks方法。这是:

public void GetBooks(Uri uri, Action<List<Books>> action)
{
    if (action == null) throw new ArgumentNullException("action");
    uri.DownloadString(t =>
    {
        var books = ParseJSON(t);
        action(books);
    });
}

它的使用方式如下:

this.GetBooks(new Uri("http://someplace.com/books.json"), books =>
{
    // Do something with `List<Books> books`
});

这应该是整洁而简单的。

现在,您可能希望通过以下两种方式扩展它。

您可以创建具有此签名的ParseJSON重载:

void ParseJSON(string text, Action<List<Books>> action)

然后你可以完全取消GetBooks方法并写下来:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
    // `string t` is also in scope here
}));

现在你有一个漂亮的流畅风格,可组合的操作。作为奖励,下载的字符串t也在范围内,因此您可以轻松地记录它或者在需要时进行其他处理。

您可能还需要处理异常,这些可以像这样添加:

public static void DownloadString(
    this Uri uri,
    Action<string> action,
    Action<Exception> exception)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = (WebClient)null;

    Action<Action> catcher = body =>
    {
        try
        {   
            body();
        }
        catch (Exception ex)
        {
            ex.Data["uri"] = uri;
            if (exception != null)
            {
                exception(ex);
            }
        }
        finally
        {
            if (webclient != null)
            {
                webclient.Dispose();
            }
        }
    };

    var handler = (DownloadStringCompletedEventHandler)null;        
    handler = (s, e) =>
    {
        var result = (string)null;
        catcher(() =>
        {   
            result = e.Result;
            webclient.DownloadStringCompleted -= handler;
        });
        action(result);
    };

    catcher(() =>
    {   
        webclient = new WebClient();
        webclient.DownloadStringCompleted += handler;
        webclient.DownloadStringAsync(uri);
    });
}

然后,您可以将非错误处理DownloadString扩展方法替换为:

public static void DownloadString(this Uri uri, Action<string> action)
{
    uri.DownloadString(action, null);
}

然后使用错误处理方法,你会这样做:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
}), ex =>
{
    // Do something with `Exception ex`
});

最终结果应该相当简单易用和阅读。我希望这会有所帮助。

答案 1 :(得分:0)

假设您没有编写ASP.NET应用程序。

您是否考虑过使用Background Worker组件?对于不应该占用UI的长时间运行任务,它是获得多线程功能的简洁方法。例如,您可以使用ProgressChanged事件对UI执行更新,后台工作程序和后台工作程序类将确保创建BW的线程是执行ProcessChanged和WorkComplete事件的线程。因此,如果您从UI创建BW并将其设置为工作,那么您可以从那里安全地更新UI。

以下是MS http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

的快速文章

另一个非常好的链接http://www.albahari.com/threading/part3.aspx#_BackgroundWorker

- edit-- 我查看了链接,他似乎正在做的是完全实现Cooporative Cancellation模式。这是后台线程通过例行检查变量并取消它是否正确来优雅地支持取消的地方。 BW是这种模式的实现。

如果你想要一些非常简单的东西,那么你可以尝试使用ThreadPool

ThreadPool.QueueUserWorkItem(DoWork);
public void DoWork(){
    //Just remember that this code happens in a seperate thread so don't update 
    //the UI. It will throw an exception. You would need to call 
    //Form.BeginInvoke(UpdateFunction) in order to update the UI 
    DoSomethingInteresting();
}
相关问题