【问题标题】:CrossThreading issue with BackgroundWorker and statusstrip updateBackgroundWorker 和 statusstrip 更新的跨线程问题
【发布时间】:2012-03-08 05:29:52
【问题描述】:

我一直在开发一个使用 BackgroundWorker 定期执行 ping 操作的工具。我遇到了 BackgroundWorker ProgressChanged 事件的问题。 ProgressChanged 事件的代码如下:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
       {
           ProgressUpdated update = (ProgressUpdated)e.UserState;
           if (sender.ToString() == "System.ComponentModel.BackgroundWorker")
           {
               toolStripStatusLabel1.Text = update.GeneralStatus;
               toolStripProgressBar1.Value = update.ProgressStatus;
               toolStripStatusLabel2.Text = update.SpecificStatus;
           }
           else
           {
               toolStripStatusLabel1.Text = update.GeneralStatus;
               toolStripProgressBar2.Value = update.ProgressStatus;
               toolStripStatusLabel3.Text = update.SpecificStatus;
           }
       }

ProgressChanged 事件在更新第一个值的 BackgroundWork 中调用,在 ping 完成时从 pingcompletedcallback 事件中调用。当 ProgressChanged 事件从 PingCompletedCallback 事件运行时,我只遇到了跨线程问题。它在更新第二个进度条时抛出错误。

我似乎无法弄清楚为什么其中一个电话会发生这种情况,而另一个电话却没有。

PingCompletedCallBack 是否发生在 BackgroundWorker 线程上,这就是它导致跨线程问题的原因?

如果是这样,我该如何引发事件,以便在 UI 线程而不是后台工作线程上处理它?

编辑:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
         BackgroundWorker worker = sender as BackgroundWorker;
         // creates ping and sends it async
         ProgressUpdated args = new ProgressUpdated(string1, int1, string 2);
         worker.ReportProgress(0,args);
         // rest of thread for cleanup when cancellation is called
    }
private void PingCompletedCallback(object sender, PingCompletedEventArgs e)
    {
            // handle the ping response
            ProgressUpdated update = new ProgressUpdated(string1, int1, string2);
            ProgressChangedEventArgs changed = new ProgressChangedEventArgs(1,update);
            backgroundWorker1_ProgressChanged(this, changed);
            // handle other types of responses

    }

我认为使用事件是为了允许线程分离。 Aka 工作线程引发 UI 线程正在侦听的事件,然后在 UI 线程上处理引发的事件。

由于我的理解有误,PingCompletedCallBack 是否可以访问backgroundworker 的ReportProgress 方法?

然后我可以更改 PingCompletedCallback:

ProgressChangedEventArgs changed = new ProgressChangedEventArgs(1,update);
backgroundWorker1_ProgressChanged(this, changed);

到:

backgroundWorker1.ReportProgress(1, update);

或者我需要以其他方式更改它吗?

感谢大家的帮助。

编辑 2:

Changed ProgrssChanged 事件

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
       {
           ProgressUpdated update = (ProgressUpdated)e.UserState;
           toolStripStatusLabel1.Text = update.GeneralStatus;
           toolStripProgressBar1.Value = update.ProgressStatus;
           toolStripStatusLabel2.Text = update.SpecificStatus;
       }

然后我创建了第二个更新事件

private void PingUpdate (object sender, ProgressUpdated e)
    {
         toolStripStatusLabel1.Text = e.GeneralStatus;
         toolStripProgressBar2.Value = e.ProgressStatus;
         toolStripStatusLable3.Text = e.SepcificStatus;
    }

我唯一剩下的就是从 PingCompletedCallback 调用新事件,使其在 UI 线程上执行。这是使用 Invoke 语句的地方,还是应该在新事件中使用 Invoke?

【问题讨论】:

  • 旁注:最好说“sender is BackgroundWorker”而不是 sender.ToString()。由于 ProgressChanged 在 UI 线程上执行,因此不需要 Invoke。
  • 您不应该在 backgroundWorker1_ProgressChanged 方法中使用任何类型的调用。使用 ReportProgress 报告进度,因为这正是这些后台工作者的目标;将后台任务与 GUI 同步。不要调用 backgroundWorker1_ProgressChanged(this, changed),因为它将从工作线程中调用。

标签: c# winforms backgroundworker multithreading statusstrip


【解决方案1】:

BackgroundWorker 的文档指出您不应通过 DoWork 方法操作 UI 对象,并且应通过 ReportProgress 对 UI 对象进行任何更改。我没有看过反射器,但它可能正在为您执行隐藏的“调用”。无论引发您的PingCompleted 事件,都可能在工作线程或其他主线程的线程中执行。

您将在 Visual Studio 调试器的线程窗口中看到 DoTask 不在主线程上执行;但是,当调用ReportProgress 时,处理程序在主线程上执行。由于您的控件可能是在主线程上创建的,因此您看不到异常。

现在,如果您尝试在 DoWork 方法中显式调用 backgroundWorker1_ProgressChanged,则 backgroundWorker1_ProgressedChanged 将在执行 DoWork 方法的同一线程上执行,或者在您的情况下,将在引发 PingCompleted 的方法上执行事件:

您可以通过在 backgroundWorker1_ProgressChanged 处理程序中添加 InvokeRequired 检查来解决此跨线程异常,或者将 PingCompleted 处理程序路由到调用 ReportProgress

编辑:

PingCompleted 处理程序调用ReportProgress 将不起作用,因为您将丢失原始发件人。

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (InvokeRequired)
    {
        Invoke(new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged), sender, e);
        return;
    }

    // The rest of your code goes here
}

编辑 2 响应:

private void PingUpdate (object sender, ProgressUpdated e)
{
     if (InvokeRequired)
     {
        Invoke(new Action<object, ProgressUpdated>(PingUpdate), sender, e);
        return;
     }

     toolStripStatusLabel1.Text = e.GeneralStatus;
     toolStripProgressBar2.Value = e.ProgressStatus;
     toolStripStatusLable3.Text = e.SepcificStatus;
}

【讨论】:

  • 感谢视觉效果,我不知道我可以查看如此详细的线程。 PingCompletedCallBack 是否可以访问 backgroundWorker 以调用 ReportProgress?
  • 我刚刚注意到您的处理程序有两个不同的进度条,您使用发送方来确定要更新哪个进度条。如果您使用路由方法,那么您的 sender 将始终是后台工作人员。您在这里至少有两个选择。为 ping 完成事件创建一个单独的处理程序,并在那里更新 UI 组件(使用 Invoke)。这将摆脱讨厌的 if (sender is ...) 检查。如果您坚持使用一个处理程序,那么您将需要在进度处理程序中添加 Invoke 检查
  • 我想我会将更新调用分开,以便消除尴尬的 if (sender is... ) 检查。在更新事件中执行调用还是在调用事件时执行调用更好?见编辑 2
  • 希望您理解这是个人偏见(if(sender is ...),可能不会被其他人分享。既然您已经发布了您的PingCompletedCallBack代码,我认为您可以替换所有代码在那里更新您的控件的代码(记得使用调用)。老实说,我不知道哪种方法更好,但您不必定义新的处理程序。
  • 我确实意识到这是个人偏见。我不需要为每个 toolStrip 项调用 Invoke 吗?我可以在 PingCompletedCallback 中使用 Invoke 还是应该保留在 PingUpdate 事件中?感谢您将您的回复标记为答案的所有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
相关资源
最近更新 更多