【问题标题】:C# multithreading - updating the GUI with background eventsC# 多线程 - 使用后台事件更新 GUI
【发布时间】:2011-04-30 20:47:47
【问题描述】:

我是 C# 和多线程的新手,所以如果这是一个重复的问题,我深表歉意,但作为一个新手,我的问题似乎与我读过的其他问题略有不同。

我的 GUI 在一个(主)线程中运行。它调用在单独线程中运行的后台任务(在 dll 中——我也在编写)。 dll 不了解 GUI(即它不能引用 GUI 类)。

现在,假设我想根据 dll 线程的状态更新 GUI 上的进度条 -> 我正在做的是在 dll 中创建一个事件,每 X% 都会触发并且 GUI 将订阅到这个事件。触发事件时,GUI 将更新进度条。

我的问题:

  1. 创建事件的方法是不是最好的方法(记住 dll 不能引用 GUI)?
  2. 如何确保我的上述方法是“事件安全”的?我应该在事件中传递进度百分比以确保线程安全还是还有更多?
  3. 更新 GUI 时是否需要使用 Invoke?我看到一个帖子建议我这样做,但我不明白为什么,因为更新栏是在 GUI 线程中完成的??!

希望你能帮我澄清一下!

谢谢

【问题讨论】:

  • “事件安全”是什么意思?
  • 在此处查看 BackgroundWorker 类的 cmets:stackoverflow.com/questions/1506838/…
  • 我只想对所有回复表示感谢。他们都帮助回答了我的问题。很高兴看到这样的帮助!

标签: c# multithreading


【解决方案1】:

1.-我一直使用这种方法,是的,它会起作用

2.-只需将一个 int 传递给事件处理程序,变量就可以安全读取。但是,当您从代码中触发事件时,请这样做

private void UpdatePercentage(int a)
{
    var myEvent = PercentageUpdatedEvent
    if(myEvent != null)
         myEvent(this, new ProgressBarEventArgs(a));
}

这样做的原因是,如果在 null 检查和调用之间取消订阅事件,您将不会收到异常。

3.-正如其他人所提到的,您需要调用 Invoke,因为该事件将在 dll 的线程上运行。但是对于控件,在没有 EndEnvoike 的情况下调用 BeginInvoke 是合法的,因此该调用在 dll 的线程上将是非阻塞的。

这是我一直使用的模式

private myClass_OnPercentageUpdatedEvent(object a, ProgressBarEventArgs e)
{
    if(progressBar.InvokeRequired)
        progressBar.BeginInvoke((Action<object,ProgressBarEventArgs>)myCless_OnPercentageUpdatedEvent, a, e);
    else
    {
        progressBar.Value = e.Value;
    }
}

【讨论】:

  • 这真的很有帮助。非常感谢。它终于点击了(可能对你们所有人都很明显),即使事件处理程序是在 GUI 类中编写的,它仍然在 dll 线程中运行,就像它被“调用”的地方一样!现在我明白了为什么我们需要 Invoke 并且这澄清了它。我同意我需要了解它为什么/如何工作而不是使用 BackgroundWorker 等的其他帖子。
【解决方案2】:

查看 BackgroundWorker 类。听起来它非常适合您的场景。

MSDN 上的这个链接解释了如何使用它:http://msdn.microsoft.com/en-us/library/cc221403%28VS.95%29.aspx

【讨论】:

  • BackgroundWorker 正在尝试帮助您解决运行后台操作的任务,而无需任何关于线程和 UI 工作的真正知识,但如果您确实了解事件是如何引发的,并没有真正提供太多帮助以及如何使用调度程序。我强烈建议任何想要编写多线程代码的人至少尝试了解在后台工作人员等抽象的底层发生了什么。
  • 我同意了解幕后发生的事情很重要,但我认为如果您不了解幕后发生的事情,BackgroundWorker 可能是最安全的实现方式。
【解决方案3】:

请记住,在大多数情况下,从后台任务引发的事件也会在后台线程上运行。根本不会自动发生线程上下文切换。

要了解原因,您必须考虑事件是什么;只是某种类型的Delegate 对象。您正在从主线程为该事件设置一个委托......但该委托实际上将在后台线程中调用,在触发事件的代码中。

所以是的;您需要确保您正在从该事件处理程序中移动事物以在 GUI 线程上运行。

【讨论】:

  • 这说明了这一点:“但该委托实际上会在后台线程中被调用,在触发事件的代码中。”
【解决方案4】:

要回答 (3),您需要使用 Invoke。事件处理程序将从后台线程运行,而不是 GUI 线程。

【讨论】:

    【解决方案5】:

    如果你分离一个线程,你需要创建一个委托,它可以使用适当的参数安全地调用你的主线程。

    delegate void UpdateDelegate(int val)
    void Update(int val)
    {
      if(this.InvokeRequired())
      {
         Invoke(new UpdateDeleage(Update),new object[] {val});
         return;
      }
      this.MyProgressBar.Value = val;
    }
    

    从您的单独线程调用更新,就像从您的主线程调用它一样。一旦线程确定您的主线程需要调用以传递值,它将使用您的委托调用它,并使用您传递的参数。否则,它只会跳过该块并设置您的值。

    例如

    ...
    
    new Thread(()=>IncrementValues()).Start();
    
    ...
    
    void IncrementValues()
    {
       while(true)
       Update(new Random(0,10));
    }
    

    【讨论】:

      【解决方案6】:

      我有on my blog 几种不同的方法来解决这个问题,各有优缺点。总之,我推荐使用Task 类。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-14
        • 1970-01-01
        相关资源
        最近更新 更多