Reputation: 3953
I have the following code which updates my progress bar and status bar from a backgroundworker. I run the same backgroundworker twice. The first time I run it I call it from the MainWindow constructor it works fine. At the end of the constructor I setup a timer to call the method every so often.
System.Threading.TimerCallback timerCallback = new System.Threading.TimerCallback(RefreshWebDataTimer);
timer = new System.Threading.Timer(
timerCallback, null,
Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD,
Dictionary.MS_TIMER_REFRESH_PERIOD);
When calling it from the timer I get the following error:
A first chance exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll Additional information: The calling thread cannot access this object because a different thread owns it.
I added some debug and indeed the Dispatcher Thread is on a different thread from the timer and the same thread from the original run.
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
System.Diagnostics.Debug.Print("Current Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
System.Diagnostics.Debug.Print("Dispatcher Thread: {0}", progressBar.Dispatcher.Thread.ManagedThreadId);
this.progressBar.Visibility = Visibility.Visible;
this.progressBar.Value = e.ProgressPercentage;
if (e.UserState != null)
{
this.statusBar.Text = e.UserState.ToString();
}
}
Current Thread: 22 Dispatcher Thread: 7
I was under the impression that the ProgressChanged
and RunWorkerCompleted
events always ran on the main UI thread in order to solve this problem and be able to do UI updates. Apparently, I misunderstand what is going on here.
I updated my solution to use the Dispatcher as follows:
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
progressBar.Dispatcher.BeginInvoke(new OneArgIntDelegate(updateProgressBar), e.ProgressPercentage);
if (e.UserState != null)
{
progressBar.Dispatcher.BeginInvoke(new OneArgStrDelegate(updateStatusBar), e.UserState.ToString());
}
}
private void updateStatusBar(string Text)
{
this.statusBar.Text = Text;
}
private void updateProgressBar(int ProgressPercentage)
{
this.progressBar.Visibility = Visibility.Visible;
this.progressBar.Value = ProgressPercentage;
}
This solution worked but I thought the whole point of the BackgroundWorker was that I didn't have to do this. Can someone explain my incorrect assumption and what is really going on. Is there a way to do this WITHOUT the dispatcher by setting up the timer differently?
Thanks,
Harrison
Upvotes: 3
Views: 2235
Reputation: 43596
I was under the impression that the ProgressChanged and RunWorkerCompleted events always ran on the main UI thread in order to solve this problem and be able to do UI updates. Apparently, I misunderstand what is going on here.
The BackgroundWorkers ProgressChanged is called back to the thread that owns the BackroundWorker, this is not aways the UI thread, When you create the BackgroundWorker
the second time it is being created on another thread so the ProgressChanged
will be invoked on the thread that created the BackgroundWorker
in this case the timer thread.
You can ether invoke the RefreshWebDataTimer
from the Timer
to the UI thread or use a DispatcherTimer
to ensure the RefreshWebDataTimer
is called on the UI thread.
Option 1:
timer = new System.Threading.Timer(
(o) => Dispatcher.Invoke((Action)RefreshWebDataTimer),
null,
Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD,
Dictionary.MS_TIMER_REFRESH_PERIOD);
Option 2:
timer = new DispatcherTimer(
TimeSpan.FromSeconds(1),
DispatcherPriority.Background,
(s, e) => RefreshWebDataTimer(),
Dispatcher);
Upvotes: 2