Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.net c# best way to wait for async events to complete and still have a "synchronous" flow in your code

My generalized question is this: how do you write asynchronous code that is still clear and easy to follow, like a synchronous solution would be?

My experience is that if you need to make some synchronous code asynchronous, using something like BackgroundWorker, you no longer have a series of easy to follow program statements that express your overall intent and order of activities, you end up instead with a bunch of "Done" Event Handlers, each of which starts the next BackgroundWorker, producing code that's really hard to follow.

I know that's not very clear; something more concrete:

Let's say a function in my WinForms application needs to start up some amazon EC2 instances, wait for them to become running, and then wait for them to all accept an SSH connection. A synchronous solution in pseudo code might look like this:

instances StartNewInstances() {
    instances = StartInstances()
    WaitForInstancesToBecomeRunning(instances)
    WaitForInstancesToAcceptSSHConnection(instances).
    return (instances)
    }

That's nice. What is happening is very clear, and the order of program actions is very clear. No white noise to distract you from understanding the code and the flow. I'd really like to end up with code that looks like that.

But in reality, I can't have a synchronous solution .. each of those functions can run for a long time, and each needs to do things like: update the ui, monitor for time-outs being exceeded, and retry operations periodically until success or time-out. In short, each of these needs to be happening in the background so the foreground UI thread can continue on.

But if I use solutions like BackgroundWorker, it seems like I don't end up with nice easy to follow program logic like the above. Instead I might start a background worker from my UI thread to perform the first function, and then my ui thread goes back to the UI while the worker thread runs. When it finishes, its "done" event handler might start the next Background Worker. WHen it finishes, its "done" event handler might start the last BackgroundWorker, and so on. Meaning you have to "follow the trail" of the Done Event handlers in order to understand the overall program flow.

There has to be a better way that a) lets my UI thread be responsive, b) let's my async operations be able to update the ui and most importantly c) be able to express my program as series of consecutive steps (as I've shown above) so that someone can understand the resultant code

Any and all input would be greatly appreciated! Michael

like image 757
Michael Ray Lovett Avatar asked Jul 26 '12 17:07

Michael Ray Lovett


2 Answers

My generalized question is this: how do you write asynchronous code that is still clear and easy to follow, like a synchronous solution would be?

You wait for C# 5. It won't be long now. async/await rocks. You've really described the feature in the above sentence... See the Visual Studio async homepage for tutorials, the language spec, downloads etc.

At the moment, there really isn't a terribly clean way - which is why the feature was required in the first place. Asynchronous code very naturally becomes a mess, especially when you consider error handling etc.

Your code would be expressed as:

async Task<List<Instance>> StartNewInstances() {
    List<Instance> instances = await StartInstancesAsync();
    await instances.ForEachAsync(x => await instance.WaitUntilRunningAsync());
    await instances.ForEachAsync(x => await instance.WaitToAcceptSSHConnectionAsync());
    return instances;
}

That's assuming a little bit of extra work, such as an extension method on IEnumerable<T> with the form

public static Task ForEachAsync<T>(this IEnumerable<T> source,
                                   Func<T, Task> taskStarter)
{
    // Stuff. It's not terribly tricky :(
}
like image 52
Jon Skeet Avatar answered Oct 19 '22 23:10

Jon Skeet


On the off chance that you can't wait for 5 as Jon rightly suggests, I'd suggest that you look at the Task Parallel Library (part of .NET 4). It provides a lot of the plumbing around the "Do this asynchronously, and when it finishes do that" paradigm that you describe in the question. It also has solid support for error handling in the asynchronous tasks themselves.

like image 40
Chris Shain Avatar answered Oct 19 '22 23:10

Chris Shain