2011-09-20 97 views
2

我有我希望我的应用程序访问的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

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

+0

你想包围什么? –

+0

那么最后我想要一个名为GetBooksAsync()的异步方法,我也有GetBooksCompleted。 –

回答

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` 
}); 

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

+0

虽然我不一定同意“需要您订阅事件以获取结果的异步操作非常痛苦”,但这是一个非常好的解决方案。好工作。 – Mac

+0

好工作!尽管最终我以一种更简单的方式解决了问题。 –

0

假设您不在编写ASP.NET应用程序。

你看过使用Background Worker组件吗?对于长时间运行的任务,不应该将UI绑定在一起,这是获得多线程功能的一种干净而简单的方法。例如,您可以使用ProgressChanged事件执行UI更新,后台工作人员和后台工作人员将确保创建BW的线程执行ProcessChanged和WorkComplete事件。因此,如果您从用户界面创建了BW并将其设置为可用,那么您可以从那里安全地更新UI。

下面是从MS http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx快速文章

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

- 编辑 - 我看着链接,他似乎做什么,是全面落实Cooporative取消的模式。这是后台线程将通过常规检查变量并取消它是否正确来优雅地支持取消的地方。 BW是这种模式的实现。

如果你想很简单的东西,那么你可以尝试只使用一个线程池

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(); 
}