views:

38

answers:

3

I understand that an event handler executes on whatever thread that invoked the event. I further understand the need to update form controls only from the thread that created the controls. I am assuming that the UI thread is the one that created the form for the purpose of this question.

If the event is the result of a posted message, such as a paint message, isn't the handler then decoupled from the originating thread? If that is true, then any thread could invoke invalidation operations and the resulting paint would always occur on the UI thread since it is the one handling form messages.

This is how it maps out in my head at near 2:00 AM with a long-empty snack bowl by my side. Please clarify and correct so I can have a proper understanding of the mechanisms at work.

+4  A: 

MSDN:

Calling the Invalidate method does not force a synchronous paint; to force a synchronous paint, call the Update method after calling the Invalidate method.

So, Invalidate can be called from any thread, and Update only from UI thread. In any case, to be 100% sure that you don't use invalid cross-thread calls, set Control::CheckForIllegalCrossThreadCalls property to true in the beginning of the program. This causes any invalid call to fail immediately, you don't need to guess.

Alex Farber
+1  A: 

First, See Alex's answer.

Second, note that just because you do something that results in something ending up in the message pump, thereby causing an event on the UI thread, does not absolutely mean that the initiating action had to happen on that same thread.

I'm speaking very broadly here; but note that any code could internally marshal things over to the UI thread by calling Control.Invoke() or an equivalent.

In your own application code, you have to be sure to do that, of course. But I'm just suggesting that if you find code somewhere that seems to violate the principle here, it's probably Invoke()'ing somewhere for you.

Andrew Barber
A: 

Even though it may technically be possible to do this, you will get into trouble. The code for Invalidate is the following:

public void Invalidate(bool invalidateChildren) {
    if (IsHandleCreated) {
        if (invalidateChildren) {
            SafeNativeMethods.RedrawWindow(new HandleRef(window, Handle),
                                            null, NativeMethods.NullHandleRef,
                                            NativeMethods.RDW_INVALIDATE |
                                            NativeMethods.RDW_ERASE |
                                            NativeMethods.RDW_ALLCHILDREN);
        }
        else {
            // It's safe to invoke InvalidateRect from a separate thread.
            using (new MultithreadSafeCallScope())
            {
                SafeNativeMethods.InvalidateRect(new HandleRef(window, Handle),
                                                null,
                                                (controlStyle & ControlStyles.Opaque) != ControlStyles.Opaque);
            }
        }

        NotifyInvalidate(this.ClientRectangle);
    }
}

There are a few problems here:

  1. If the Form is disposed after the IsHandleCreated check is done, the handle will be gone;

  2. The NotifyInvalidate which calls OnInvalidated which triggers the Invalidated event will be called on the calling thread. The handlers registered to that event may not expect it to have been called from a different thread;

  3. Even though this states that InvalidateRect can be called from a separate thread, it does not state whether RedrawWindow can. The question thus becomes whether you call Invalidate(false) (or Invalidate(), this maps to false) or you're calling Invalidate(true).

Long story short. You shouldn't call this from a different thread. You should call all methods that interact with a Control (so also Form) through Invoke or BeginInvoke.

Pieter