3

In my application, I create a new UI-Thread with the fallowing code:

Thread thread = new Thread(() =>
    {
        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        System.Windows.Threading.Dispatcher.Run();
    }) { IsBackground = true };
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();

This give me the fallowing problem:

In the constructor of the MyWindow class, a BackgroundWorker is executed. In the RunWorkerCompleted there should a Control be updated with some data, which the BackgroundWorker is calculating.

I have build a small sample, which is illustrating this:

public partial class MyWindow : Window {
    public MyWindow() {
        InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        this.Title = "Calculated title";
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}

In bw_RunWorkerCompleted() I get an InvalidOperationException (The calling thread cannot access this object because a different thread owns it.). It looks like, that the BackgroundWorker is not returning to the correct UI-Thread from which it was started from.

Can someone help me, what I can do to solve this problem? I can't change the Code which is executing the BackgroundWorker, because it is in a framework, which I use. But I can do something else in the RunWorkerCompleted-Event. But I have no idea, how to solve this problem.

BennoDual
  • 5,865
  • 15
  • 67
  • 153
  • Why do you "create a new UI-Thread" at all? Not necessary, not productive and the source of this problem. – H H Nov 08 '11 at 22:37

6 Answers6

4

The problem is that the window is getting created too soon. The thread doesn't have a synchronization context yet. You can see this is the debugger by setting a breakpoint on BGW constructor call and look at Thread.CurrentThread.ExecutionContext.SynchronizationContext. It's null. Which is what BGW uses to decide how to marshal the RunWorkerCompleted event. Which no synchronization context, the event runs on a threadpool thread and that invokes wrath.

You need to get the dispatcher initialized sooner. Not 100% this is the correct way but it did seem to work:

        Thread thread = new Thread(() => {
            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => {
                MyWindow windowInAnotherThread = new MyWindow();
                windowInAnotherThread.Show();
            }));
            System.Windows.Threading.Dispatcher.Run();
        }) { IsBackground = true };
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

You also have to explicitly force the thread to shutdown. Add this method to MyWindow:

    protected override void OnClosed(EventArgs e) {
        Dispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
    }
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Or delay creating the bgw, now that I think about it :) – Hans Passant Nov 08 '11 at 22:39
  • You can't call BeginInvoke like this, as it's going to push the Window creation into the UI thread (Dispatcher.BeginInvoke is the current window's dispatcher, not the new thread one...) – Reed Copsey Nov 08 '11 at 22:49
  • I think this would work if you used `Dispatcher.CurrentDispatcher.BeginInvoke(...)` – Reed Copsey Nov 08 '11 at 22:51
  • Yes, that's what I feared. Better delete that. – Hans Passant Nov 08 '11 at 22:51
  • 1
    I started writing this - then figured I should test it, which is how I came up with my version... Just tried, if you use `Dispatcher.CurrentDispatcher.BeginInvoke`, this approach works. – Reed Copsey Nov 08 '11 at 22:53
  • Hi Hans - this is working great - also in my productiv Application. Thank you very much. I have one more small question about this: Shouldn't my new UI-Thread be terminated, when I close MyWindow? Does I have to change something, that the thread will be terminated after closing the MyWindow? – BennoDual Nov 08 '11 at 23:28
2

Ran into similar issue. Based on note 1 and 2 below I created UIBackgroundWorker. May be it can help other developers who encounter this issue.

If it works then please let me know or update the design for benefit of other developers.

public class UIBackgroundWorker : BackgroundWorker
{

    private System.Windows.Threading.Dispatcher uiDispatcher;
    public SafeUIBackgroundWorker(System.Windows.Threading.Dispatcher uiDispatcher)
        : base()
    {
        if (uiDispatcher == null)
            throw new Exception("System.Windows.Threading.Dispatcher instance required while creating UIBackgroundWorker");
        else
            this.uiDispatcher = uiDispatcher;
    }

    protected override void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnProgressChanged(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnProgressChanged(e)));
    }

    protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnRunWorkerCompleted(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnRunWorkerCompleted(e)));
    }
}
purvin
  • 21
  • 1
1

The problem is that you need to setup the SynchronizationContext. This is normally not an issue, as Dispatcher.Invoke will set it up for you, but since you're using BackgroundWorker in the constructor (which is fired prior to Dispatcher.Run), no context is setup.

Change your thread creation to:

Thread thread = new Thread(() =>
    {
        // Create the current dispatcher (done via CurrentDispatcher)
        var dispatcher = Dispatcher.CurrentDispatcher;
        // Set the context
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(dispatcher));

        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        Dispatcher.Run();
    });

thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

This will cause it to run correctly, as the SynchronizationContext will be in place prior to the construction of the Window.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
0

Try providing a getter and setter for your BackgroundWorker inside MyWindow. And pass BackgroundWorker object via setter method to Mywindow. That should solve the problem, i guess.

Ravi Bhatt
  • 3,147
  • 19
  • 21
0

You need to use a delegate method and an invoke in the calling function. There's a good example here: http://msdn.microsoft.com/en-us/library/aa288459(v=vs.71).aspx

Using your code,

    public partial class MyWindow : Window {


    delegate void TitleSetter(string title);

    public MyWindow() {
            InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void SetTitle(string T)
    {
      this.Title = T;
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

      try    
        {
        TitleSetter T = new TitleSetter(SetTitle);
        invoke(T, new object[]{"Whatever the title should be"}); //This can fail horribly, need the try/catch logic.
        }catch (Exception){}
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}
Jason
  • 121
  • 8
0

I think simply moving your background worker thread setup code into the "Load" event instead of the constructor should be just fine.

Eric Liprandi
  • 5,324
  • 2
  • 49
  • 68