Szymon Seliga
Szymon Seliga

Reputation: 824

How to write a wrapper around an asynchronous method?

I have a JSON API which I want my application to access. 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. 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?

Upvotes: 2

Views: 1368

Answers (2)

Enigmativity
Enigmativity

Reputation: 117064

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.

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.

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. 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:

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

Then you could do away with the GetBooks method altogether and just write this:

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.

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:

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.

Upvotes: 7

Jason Turan
Jason Turan

Reputation: 1352

Assuming you are aren't writing an ASP.NET app.

Have you looked into using a Background Worker component? For long running tasks that shouldn't tie up the UI it is a clean and easy way to get multithreading capabilites. 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. So if you made the BW from the UI and set it off to work then you can update the UI safely from there.

Here's a quick article from MS http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

Another really good link 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. 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.

If you want something really simple then you can try just using a 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();
}

Upvotes: 0

Related Questions