简体   繁体   中英

BackgroundWorker.ProgressChanged event not on Dispatcher Thread

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

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.

  • First Call: UIThread => BacgroundWorker => Progress => UIThread
  • SecondCall: TimerThread => BacgroundWorker => Progress => TimerThread

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM