Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
    
What is fragile about it? It gives the user an indication something is being processed. Add some dynamic indication work is being processed and that seems like perfectly good UI to me. –  Steven Jeuris Oct 10 '11 at 12:01
    
P.S.: Is .NET 4.5's C# called C# 5? –  Steven Jeuris Oct 10 '11 at 12:02
1  
Just curious why this question is here and not SO? This is definitely about code so it would belong there I think ... –  C. Ross Oct 10 '11 at 12:40
    
@C.Ross, This is more of a design/UI question. There is not really a technical point that needs to be explained here. –  Morgan Herlocker Oct 10 '11 at 12:53
    
We have a similar problem in ajax HTML forms where a user hits the submit button, an ajax request is send and we then want to block the user from hitting submit again, disabling the button is a very common solution in that situation –  Raynos Oct 10 '11 at 14:59

6 Answers 6

up vote 12 down vote accepted

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.

share|improve this answer
    
Thanks Eric, I guess this is the definitive answer - it's up to the application developer. –  Nicholas Butler Oct 10 '11 at 16:36

Why do you think disabling the button before the await and then re-enabling it when the call completes is fragile? Is it because you are worried that the button will never get re-enabled?

Well, if the call doesn't return then you can't make the call again, so it seems that this is the behaviour you want. This indicates an error state that you should be fixing. If your async call times out then this could still leave your application in an indeterminate state - again one in which having a button enabled could be dangerous.

The only time this might get messy is if there are several operations that affect the state of a button, but I think those situations should be very rare.

share|improve this answer
    
Of course you have to handle errors - I have updated my question –  Nicholas Butler Oct 10 '11 at 12:32

I'm not sure if this applies to you since I usually use WPF, but I see you referencing a ViewModel so it might.

Instead of disabling the button, set an IsLoading flag to true. Then any UI element you want to disable can be bound to the flag. The end result is that it becomes the UI's job to sort out it's own enabled state, not your Business Logic.

In addition to that, most buttons in WPF are bound to an ICommand, and usually I set the ICommand.CanExecute equal to !IsLoading, so it automatically prevents the Command from executing when IsLoading is equal to true (also disables the button for me)

share|improve this answer

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.

There is nothing fragile about this, and it is what the user would expect. If the user needs to wait before hitting the button again then make them wait.

I can see how certain control scenarios could make deciding which controls to disable/enable at any one time pretty complex, but is it suprising that managing a complex UI would be complex? If you have too many scary side effects from a particular control, you can always just disable the entire form and throw a "loading" whirly-gig over the whole thing. If this is the case on every single control, then your UI is probably not very well designed in the first place (tight coupling, poor control grouping, etc.).

share|improve this answer
    
Thanks - but how to manage the complexity? –  Nicholas Butler Oct 10 '11 at 13:17
    
One option would be to put related controls into container. Disable/enable by control container, not by control. ie: EditorControls.Foreach(c => c.Enabled = false); –  Morgan Herlocker Oct 10 '11 at 17:42

Disabling the widget is the least complex and error-prone option. You disable it in the handler before starting the async operation and you re-enable it at the end of the operation. So the disable+enable for each control are kept local to handling of that control.

You can even create a wrapper, that will take a delegate and control (or more of them), disable the controls and asynchronously run something, that will do the delegate and than re-enable the controls. It should take care of exceptions (do the enable in finally), so it still works reliably if the operation fails. And you can combine it with adding message in status bar, so the user knows what he's waiting for.

You may want to add some logic to count the disables, so the system keeps working if you have two operations, that should both disable a third one, but are not otherwise mutually exclusive. You disable the control twice, so it will become enabled again only if you enable it twice again. That should cover most cases while keeping the cases separate as much as you can get.

On the other hand anything like state machine is going to become complex. In my experience all state machines I ever saw were difficult to maintain and your state machine would need to encompass the whole dialog, tying everything to everything.

share|improve this answer
    
Thanks - I like this idea. So would you have a manager object for your window that counts the number of disable & enable requests for each control? –  Nicholas Butler Oct 10 '11 at 13:25
    
@NickButler: Well, you have a manager object for each window already—the form. So I'd have a class implementing the requests and counting and just add instances to the relevant forms. –  Jan Hudec Oct 10 '11 at 14:24

Though Jan Hudec and Rachel have expressed related ideas, I would suggest using something like a Model-View architecture - express the state of the form in an object and tie the form elements to that state. This would disable the button in a way that can scale for more complex scenarios.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.