C# Async Re-entrancy Solutions – How to Prevent Issues

asynccmultithreading

So, something's been bugging me about the new async support in C# 5:

The user presses a button which starts an async operation. The call returns immediately and the message pump starts running again – that's the whole point.

So the user can press the button again – causing re-entrancy. What if this is a problem?

In the demos I've seen, they disable the button before the await call and enable it again afterwards. This seems to me like a very fragile solution in a real-world app.

Should we code some sort of state machine that specifies which controls must be disabled for a given set of running operations? Or is there a better way?

I'm tempted to just show a modal dialog for the duration of the operation, but this feels a bit like using a sledgehammer.

Anyone got any bright ideas, please?


EDIT:

I think disabling the controls that shouldn't be used while an operation is running is fragile because I think it will quickly become complex when you have a window with many controls on it. I like to keep things simple because it reduces the chance of bugs, both during the initial coding and the subsequent maintenance.

What if there is a collection of controls that should be disabled for a particular operation? And what if multiple operations are running concurrently?

Best Answer

So, something's been bugging me about the new async support in C# 5: The user presses a button which starts an async operation. The call returns immediately and the message pump starts running again - that's the whole point. So the user can press the button again - causing re-entrancy. What if this is a problem?

Let's start by noting that this is already a problem, even without async support in the language. Lots of things can cause messages to be dequeued while you're handling an event. You already have to code defensively for this situation; the new language feature just makes that more obvious, which is goodness.

The most common source of a message loop running during an event causing unwanted re-entrancy is DoEvents. The nice thing about await as opposed to DoEvents is that await makes it more likely that new tasks are not going to "starve" currently-running tasks. DoEvents pumps the message loop and then synchronously invokes the re-entrant event handler, whereas await typically enqueues the new task so that it will run at some point in the future.

In the demos I've seen, they disable the button before the await call and enable it again afterwards. I think disabling the controls that shouldn't be used while an operation is running is fragile because I think it will quickly become complex when you have a window with many controls on it. What if there is a collection of controls that should be disabled for a particular operation? And what if multiple operations are running concurrently?

You can manage that complexity the same way you'd manage any other complexity in an OO language: you abstract the mechanisms away into a class, and make the class responsible for correctly implementing a policy. (Try to keep the mechanisms logically distinct from the policy; it should be possible to change up the policy without tinkering overmuch with the mechanisms.)

If you have a form with lots of controls on it which interact in interesting ways then you are living in a world of complexity of your own making. You've got to write code to manage that complexity; if you don't like that then simplify the form so that it isn't so complex.

I'm tempted to just show a modal dialog for the duration of the operation, but this feels a bit like using a sledgehammer.

Users will hate that.