4

In my main window (Thread A), I start a new thread (Thread B) which does some work while the user waits.

Thread B fires events if there is an error or extra information is required from the user, Thread A will listen to these events.

In Thread A's event listener I need to show dialog messages to the user, I have a custom dialog window and show it using dialogWindow.showDialog(). This works fine, but causes an error when I try and set the owner of the dialog, I do this dialogWindow.Owner = Window.GetWindow(this).

The error that I get is: The calling thread cannot access this object because a different thread owns it.

What is the correct way to listen for events that are fired from a different thread?

Drahcir
  • 11,772
  • 24
  • 86
  • 128

3 Answers3

7

The event listener code will run implicitly at the thread which fires the event, so the event listener is not thread-bound.

If you want to show something in the UI as a result of event handling, you should do the marshalling yourself. Something like that:

void OnEvent(object sender, EventArgs args)
{
    // runs in the event sender's thread
    string x = ComputeChanges(args);
    Dispatcher.BeginInvoke((Action)(
        () => UpdateUI(x)
    ));
}

void UpdateUI(string x)
{
    // runs in the UI thread
    control.Content = x;
    // - or -
    new DialogWindow() { Content = x, Owner = this }.ShowDialog();
    // - or whatever
}

So: you execute your computations (if any) preferrably in the background thread, without touching the UI; after that, when you know which are the needed changes in the UI, you execute the UI-updating code in the UI thread.

The Dispatcher is a property of a control, so if your code is a part of UI, you'll have your Dispatcher for free. Otherwise, you can take the dispatcher from any of the controls (e.g., control.Dispatcher).

Vlad
  • 35,022
  • 6
  • 77
  • 199
3

Sure -> what we do is to use the SynchronizationContext. So when you start a new thread, you capture (on UI thread) the current context and pass it into the second thread as parameter.

Then on the second thread when you want to raise an event you do it this way:

    if (_uiThreadId != Thread.CurrentThread.ManagedThreadId)
    {
        _uiContext.Post(
            new SendOrPostCallback(delegate(object o) { OnYourEvent((EventArgs)o); }),
            e);
    }
    else
        OnYourEvent(e);
Martin Moser
  • 6,219
  • 1
  • 27
  • 41
  • what a really nice solution! don't know about the sync-context, nice reference! Thumbs Up! – inva Jul 12 '12 at 13:18
3

The correct way to raise an event to the UI thread from the background thread is that, the event should be raised on that Dispatcher, Key here is get the dispatcher of UIthread before hand.

UIDisaptcher.BeginInvoke((ThreadStart)(() => RaiseEventToUIThread()));

when the UI thread listens to the raised event it can set the Owner property(as the window was created by the UI thread).

neo
  • 425
  • 3
  • 11