views:

189

answers:

5

I know "why is my this framework like/not like xyz?" questions are a bit dangerous but I want to see what I'm missing.

In WinForms, you can't update the UI from another thread. Most people use this pattern:

private void EventHandler(object sender, DirtyEventArgs e)
{
    if (myControl.InvokeRequired)
        myControl.Invoke(new MethodInvoker(MethodToUpdateUI), e);
    else
        MethodToUpdateUI(e);
}

private void MethodToUpdateUI(object obj) 
{
    // Update UI
}

and more clever still is this pattern:

public static TResult SafeInvoke(this T isi, Func call) where T : ISynchronizeInvoke
{
    if (isi.InvokeRequired) { 
        IAsyncResult result = isi.BeginInvoke(call, new object[] { isi }); 
        object endResult = isi.EndInvoke(result); return (TResult)endResult; 
    }
    else
        return call(isi);
}

public static void SafeInvoke(this T isi, Action call) where T : ISynchronizeInvoke
{
    if (isi.InvokeRequired)
        isi.BeginInvoke(call, new object[] { isi });
    else
        call(isi);
}

Regardless of which is used though, everyone has to write boilerplate code to handle this incredibly common problem. Why then, has the .NET Framework not been updated to do this for us? Is it that this area of the codebase is frozen? Is it a concern that it would break backwards compatibility? Is it a concern about the confusion when some code works one way in version N and a different way in version N+1?

+4  A: 

I think that the problem is that this construct would probably need to be built into every property of all UI components in the framework. This would also need to be done by third party developers making such components.

Another option might be that the compilers added the construct around accesses to UI components, but this would add complexity to the compilers instead. As I understand it, for a feature to make way into the compiler it should

  • play well will all other existing features in the compiler
  • have an implementation cost that makes it worth the while in relation to the problem it solves

In this particular case the compiler would also need to have a way of determining whether a type in the code is a type that needs the synchronization construct around it or not.

Of course all of these are speculations, but I could imagine that this sort of reasoning lies behind the decision.

Fredrik Mörk
@Fredrik Mörk: Great answer. One detail: using the extension methods (from the 2nd code example), it wouldn't need to be built into every property.
Dinah
+5  A: 

It is built-in, the BackgroundWorker class implements it automatically. Its event handlers run on the UI thread, assuming it is properly created.

Taking a more cynical approach to this: it is an anti-pattern. At least in my code, it is extremely rare that the same method run on both the UI thread and some worker thread. It just doesn't make sense to test InvokeRequired, I know it is always is true since I wrote the code that would call it intentionally as code that runs in a separate thread.

With all the necessary plumbing that's required to make such code safe and interop correctly. Using the lock statement and Manual/AutoResetEvents to signal between the threads. And if InvokeRequired would be false then I know I have a bug in that code. Because invoking to the UI thread when the UI components are either not yet created or disposed is Very Bad. It belongs in a Debug.Assert() call, at best.

Hans Passant
@Hans Passant: I see your point about it possibly being an anti-pattern. One detail about the BackgroundWorker class: the cross-thread UI updating isn't built into `DoWork`. To get their optimization, you have to only update the UI from `ProgressChanged` (or `Completed`). I realize it's a personal preference, but I find it easier to write the boilerplate code and not have to jump through the hoops of only updating the UI via `ProgressChanged`.
Dinah
Note that `InvokeRequired` is false until a control is added to a form *and* that form has been shown. So if you e.g. test controls in unit tests without actually creating them, or if you create a form at the start and display it later, you need exactly this pattern.
nikie
Another point: If you subscribe to an event of another object, it can be a good idea to test for `InvokeRequired` in the event handler. Take `FileSystemWatcher` for example - I didn't find anything in the documentation that guarantees which thread events will be raised from. It's an implementation detail. IMO, you shouldn't rely on an implementation detail of another class, so you should use the `InvokeRequired`-Pattern.
nikie
@nikie - this is not the way it works. FSW for one raises events on a tp thread, unless you assign the SynchronizingObject property. Well documented. It needs to be, threading is never a detail that can be left undocumented. If for some reason it is undocumented, then yes, by all means use InvokeRequired.
Hans Passant
You're right, FSW was a bad example.
nikie
+2  A: 

A cleaner pattern in.NET 4 is using TPL and continuations. See http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx

Use

var ui = TaskScheduler.FromCurrentSynchronizationContext();

and now you can easily request that continuations run on the UI thread.

Hightechrider
PDC is coming up. I wonder if they'll announce anything relevant to this answer. I've seen a few people speculating on Eric Lippert's blog ( http://blogs.msdn.com/b/ericlippert/archive/2010/10/21/continuation-passing-style-revisited-part-one.aspx ) that C# 5 might exist and have features relating to this, but of course Eric can "neither confirm nor deny the existence of any unannounced hypothetical 'C# 5' product or any feature that it hypothetically might or might not implement."
Brian
A: 

If I understand your question correctly, you'd want the framework (or compiler or some other piece of technology) to include Invoke/BeginInvoke/EndInvoke around all public members of a UI object, to make it thread safe. The problem is: That alone wouldn't make your code thread safe. You'd still have to use BeginInvoke and other syncronization mechanisms very often. (See this great article on thread safety on Eric Lippert's Blog)

Imagine you write code like

if (myListBox.SelectedItem != null) 
{
    ...
    myLabel.Text = myListBox.SelectedItem.Text;
    ...
}

If the framework or compiler wrapped every access to SelectedItem and the call to Delete in a BeginInvoke/Invoke call, this wouldn't be thread safe. There's a potential race condition if SelectedItem is non-null when the if-clause is evaluated, but another thread sets it to null before the then-block is completed. Probably the whole if-then-else-clause should be wrapped in a BeginInvoke-call, but how should the compiler know this?

Now you could say "but that's true for all shared mutable objects, I'll just add locks". But that is quite dangerous. Imagine you did something like:

// in method A
lock (myListBoxLock)
{
    // do something with myListBox that secretly calls Invoke or EndInvoke
}

// in method B
lock (myListBoxLock)
{
    // do something else with myListBox that secretly calls Invoke or EndInvoke
}

This will deadlock: Method A is called in a background thread. It acquires the lock, calls Invoke. Invoke waits for a response from the UI thread's message-queue. At the same time, method B is executed in the main thread (e.g. in a Button.Click-Handler). The other thread holds myListBoxLock, so it can't enter the lock - now both threads wait on each other, neither can make any progress.

Finding and avoiding threading-bugs like these is hard enough as it is, but at least now you can see that you're calling Invoke and that it's a blocking call. If any property could silently block, bugs like these would be far harder to find.

Moral: Threading is hard. Thread-UI-interaction is even harder, because there is only one shared single message queue. And sadly, neither our compilers nor our frameworks are smart enough to "just make it work right".

nikie
+6  A: 

I thought it might be interesting to mention why it is that there's a UI thread in the first place. It is to lower the cost of the production of UI components while increasing their correctness and robustness.

The basic problem of thread safety is that a non-atomic update of private state can be observed to be half-finished on a reading thread if the writing thread is halfway done when the read happens.

To achieve thread safety there are a number of things you can do.

1) Explicitly lock all reads and writes. Pros: maximally flexible; everything works on any thread. Cons: Maximally painful; everything has to be locked all the time. Locks can be contended, which makes them slow. It is very easy to write deadlocks. It is very easy to write code that handles re-entrancy poorly. And so on.

2) Allow reads and writes only on the thread that created the object. You can have multiple objects on multiple threads, but once an object is used on a thread, that's the only thread that can use it. Therefore there will be no reads and writes on different threads simultaneously, so you don't need to lock anything. This is the "apartment" model, and it is the model that the vast majority of UI components are built to expect. The only state that needs to be locked is state shared by multiple instances on different threads, and that's pretty easy to do.

3) Allow reads and writes only on the owning thread, but allow one thread to explicitly hand off ownership to another when there are no reads and writes in progress. This is the "rental" model, and it is the model used by Active Server Pages to recycle script engines.

Since the vast majority of UI components are written to work in the apartment model, and it is painful and difficult to make all those components free-threaded, you're stuck with having to do all UI work on the UI thread.

Eric Lippert