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

从计时器调用它时,出现以下错误:

WindowsBase.dll 中发生了“System.InvalidOperationException”类型的第一次机会异常附加信息:调用线程无法访问此对象,因为不同的线程拥有它。

我添加了一些调试,实际上调度程序线程与计时器位于不同的线程上,而与原始运行的线程相同。

   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

我的印象是ProgressChangedandRunWorkerCompleted事件总是在主 UI 线程上运行,以解决这个问题并能够进行 UI 更新。显然,我误解了这里发生了什么。

我更新了我的解决方案以使用 Dispatcher,如下所示:

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 的全部意义在于我不必这样做。有人可以解释我的错误假设以及真正发生的事情。有没有办法通过不同地设置计时器来做到这一点而无需调度程序?

谢谢,

哈里森

4

1 回答 1

2

我的印象是 ProgressChanged 和 RunWorkerCompleted 事件总是在主 UI 线程上运行,以便解决这个问题并能够进行 UI 更新。显然,我误解了这里发生了什么。

BackgroundWorkers ProgressChanged 被回调到拥有 BackroundWorker 的线程,这不会离开 UI 线程,当您BackgroundWorker第二次创建它时,它是在另一个线程上创建的,因此在这种情况下将在ProgressChanged创建的线程上调用BackgroundWorker计时器线程。

  • 第一次调用:UIThread => BacgroundWorker => Progress => UIThread
  • SecondCall: TimerThread => BacgroundWorker => Progress => TimerThread

RefreshWebDataTimer您可以从调用Timer到 UI 线程或使用 aDispatcherTimer来确保在RefreshWebDataTimerUI 线程上调用 。

选项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);
于 2013-09-10T01:56:48.253 回答