Reputation: 6279
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.
Upvotes: 3
Views: 2069
Reputation: 21
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)));
}
}
Upvotes: 2
Reputation: 942338
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);
}
Upvotes: 4
Reputation: 564851
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.
Upvotes: 1
Reputation: 5574
I think simply moving your background worker thread setup code into the "Load" event instead of the constructor should be just fine.
Upvotes: 0
Reputation: 151
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);
}
}
Upvotes: 0
Reputation: 3163
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.
Upvotes: 0