BackgroundWorker.ProgressChanged事件不在Dispatcher线程上

3
我有下面的代码,它可以从后台线程更新我的进度条和状态栏。我运行相同的后台线程两次。第一次我从MainWindow的构造函数中调用它,这样它就可以正常工作。在构造函数的末尾,我设置了一个定时器来每隔一段时间调用该方法。
   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);

当我从计时器中调用它时,我会得到以下错误:

第一次出现了“System.InvalidOperationException”类型的异常,源自WindowsBase.dll。附加信息:由于不同的线程拥有它,所以调用线程无法访问此对象。

我添加了一些调试代码,确实发现Dispatcher线程在与计时器不同的线程上,但与原始运行的线程相同。

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

当前线程:22 调度器线程:7

我曾经以为ProgressChangedRunWorkerCompleted事件总是在主UI线程上运行,以解决这个问题并能够进行UI更新。显然,我误解了这里发生的事情。

我更新了我的解决方案,如下所示:

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

这个解决方案虽然可行,但我认为BackgroundWorker的整个意义在于不需要这样做。有人可以解释一下我的错误假设以及实际情况吗?是否有一种方式可以通过设置计时器来避免使用调度程序?谢谢,Harrison。

你在哪里创建BackgroundWorker?它需要在运行调度程序的线程上创建(通常是主线程)。 - Peter Ritchie
1个回答

2
我曾认为ProgressChanged和RunWorkerCompleted事件总是在主UI线程上运行,以解决这个问题并能够进行UI更新。显然,我对这里正在发生的事情有所误解。
BackgroundWorkers ProgressChanged被回调到拥有BackroundWorker的线程上,这不一定是UI线程。当您第二次创建BackgroundWorker时,它将在另一个线程上创建,因此在此情况下,ProgressChanged将在创建BackgroundWorker的线程上调用,即计时器线程。
第一次调用:UIThread => BacgroundWorker => Progress => UIThread 第二次调用:TimerThread => BacgroundWorker => Progress => TimerThread
您可以从定时器将RefreshWebDataTimer调用到UI线程上或使用DispatcherTimer来确保在UI线程上调用RefreshWebDataTimer。
选项1:
  timer = new System.Threading.Timer(
             (o) => Dispatcher.Invoke((Action)RefreshWebDataTimer),
             null,
             Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD,
             Dictionary.MS_TIMER_REFRESH_PERIOD);

选项2:

   timer = new DispatcherTimer(
                TimeSpan.FromSeconds(1),
                DispatcherPriority.Background,
                (s, e) => RefreshWebDataTimer(),
                Dispatcher);

谢谢。我会试一下的。那看起来是个很好的解决方案。 - Harrison
我正在尝试选项1,但是一直遇到问题:mscorlib.dll中发生了System.Reflection.TargetParameterCountException类型的第一次机会异常。RefreshWebDataTimer需要一个参数对象,因此我重构了您的解决方案为(o) => Dispatcher.Invoke((Action<object>)RefreshWebDataTimer),但仍然没有成功。您能帮忙吗?谢谢。 - Harrison
我使用了选项2,并进行了如下调整,现在它可以正常工作:timer = new System.Windows.Threading.DispatcherTimer( new TimeSpan(0,0,0,0,Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD), System.Windows.Threading.DispatcherPriority.Background, (s, e) => RefreshWebDataTimer(null), Dispatcher);感谢您的帮助! - Harrison

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接