简体   繁体   English

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

[英]How to write a wrapper around an asynchronous method?

I have a JSON API which I want my application to access. 我有一个JSON API,我希望我的应用程序可以访问它。 So I wrote a method. 所以我写了一个方法。

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 
}

Now I need to change DonwloadString to DownloadStringAsync. 现在,我需要将DonwloadString更改为DownloadStringAsync。 I found this tutorial . 我找到了本教程

But this just seems too complicated. 但这似乎太复杂了。 I'm trying to get this working, but am not sure if this is the right way to go. 我正在尝试使其正常工作,但不确定这是否是正确的方法。 Perhaps there is a simpler and better way? 也许有一种更简单更好的方法?

All of the async operations that require you to subscribe to events to get the results are just painful. 所有需要您订阅事件以获取结果的异步操作都非常痛苦。 I think that the simplest way to go is to abstract away the event handling into some nice extension methods and use continuation passing style (CPS) to process the results. 我认为最简单的方法是将事件处理抽象为一些不错的扩展方法,并使用连续传递样式(CPS)处理结果。

So, the first thing is to create an extension method for downloading strings: 因此,第一件事是创建用于下载字符串的扩展方法:

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

This method hides away the creation of the WebClient , all of the event handling, and the disposing and unsubscribing to clean things up afterwards. 此方法隐藏了WebClient的创建,所有事件处理以及随后清理和取消预订清理内容的工作。

It's used like this: 它的使用方式如下:

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

Now this can be used to create a GetBooks method. 现在,可以将其用于创建GetBooks方法。 Here it is: 这里是:

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

It's used like this: 它的使用方式如下:

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

That should be neat and simple. 那应该简洁明了。

Now, you may wish to extend this a couple of ways. 现在,您可能希望以几种方式扩展它。

You could create an overload of ParseJSON that has this signature: 您可以创建具有以下签名的ParseJSON重载:

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

Then you could do away with the GetBooks method altogether and just write this: 然后,您可以完全取消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
}));

Now you have a nice neat fluent-style, composable set of operations. 现在,您有了一个很好的,流畅的,可组合的操作集。 As a bonus the downloaded string, t , is also in scope so you can easily log it or do some other processing if need be. 另外,下载的字符串t也在范围内,因此您可以轻松地记录它或在需要时进行其他处理。

You may also need to handle exceptions and these can be added like so: 您可能还需要处理异常,可以像这样添加这些异常:

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

You can then replace the non-error handling DownloadString extension method with: 然后,您可以将非错误处理DownloadString扩展方法替换为:

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

And then to use the error handling method you would do this: 然后使用错误处理方法,您可以执行以下操作:

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

The end result should be fairly simple to use and read. 最终结果应该非常易于使用和阅读。 I hope this helps. 我希望这有帮助。

Assuming you are aren't writing an ASP.NET app. 假设您不是在编写ASP.NET应用程序。

Have you looked into using a Background Worker component? 您是否考虑过使用Background Worker组件? For long running tasks that shouldn't tie up the UI it is a clean and easy way to get multithreading capabilites. 对于不应该占用UI的长时间运行的任务,这是获取多线程功能的一种简单明了的方法。 For instance you can perform updates to the UI using the ProgressChanged Event and the background worker and the background worker class will ensure that the thread that created the BW is the one that executes the ProcessChanged and WorkComplete event. 例如,您可以使用ProgressChanged事件对UI进行更新,并且后台工作程序和后台工作程序类将确保创建BW的线程是执行ProcessChanged和WorkComplete事件的线程。 So if you made the BW from the UI and set it off to work then you can update the UI safely from there. 因此,如果您从UI制作了BW并将其设置为可以正常工作,则可以从那里安全地更新UI。

Here's a quick article from MS http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx 这是来自MS的快速文章http://msdn.microsoft.com/zh-cn/library/cc221403%28v=vs.95%29.aspx

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

--edit-- I looked at the link and what he appears to be doing is a full implementation of the Cooporative Cancellation pattern. --edit--我看了一下链接,他似乎正在做的是Cooporative Cancellation模式的完整实现。 This is where a background thread will support cancellation gracefully by routinely checking a variable and cancelling if it's true. 在这里,后台线程将通过定期检查变量并取消其是否为真来优雅地支持取消。 The BW is an implementation of this pattern. BW是此模式的实现。

If you want something really simple then you can try just using a ThreadPool 如果您想要简单的东西,可以尝试使用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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM