使用dispatcher.Invoke使我的线程安全吗?

时间:2009-10-06 16:30:48

标签: wpf thread-safety dispatcher

在我的WPF应用程序中,我有一个长时间运行的上传,它会引发进度事件,因为它会更新进度条。用户还有机会取消上传,否则可能会出错。这些都是异步事件,因此需要使用Dispatcher.Invoke执行它们以更新UI。

所以代码看起来像这样,ish:

void OnCancelButtonClicked(object sender, EventArgs e)
{
    upload.Cancel();
    _cancelled = true;
    view.Close();
    view.Dispose();
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        if (!cancelled)
            view.Progress = e.Value;
    }
}

假设在已调配视图上设置view.Progress将抛出错误,此代码线程是否安全?即,如果用户在进度更新时单击取消,则他/她将不得不等待进度更新,如果在执行OnCancelButtonClicked期间更新进度,则Dispatcher.Invoke调用将导致view.Progress更新到在_cancelled设置后排队,所以我不会在那里遇到问题。

或者我需要锁才能安全,la:

object myLock = new object();

void OnCancelButtonClicked(object sender, EventArgs e)
{
    lock(myLock)
    {
        upload.Cancel();
        _cancelled = true;
        view.Close();
        view.Dispose();
    }
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        lock(myLock)
        {
            if (!cancelled)
                view.Progress = e.Value;
        }
    }
}

2 个答案:

答案 0 :(得分:8)

您不必添加锁。 Dispatcher.Invoke和BeginInvoke请求不会在其他代码的中间运行(这就是它们的全部内容)。

只需要考虑两件事:

  1. 在这种情况下,BeginInvoke可能更合适,Invoke将对请求进行排队,然后阻止调用线程,直到UI线程变为空闲并完成代码执行,BeginInvoke将只对请求进行排队而不会阻塞。
  2. 某些操作,尤其是打开窗口(包括消息框)或进行进程间通信的操作,可能允许排队的调度程序操作运行。
  3. 编辑:首先,我没有引用,因为关于这个主题的MSDN页面的细节非常低 - 但我已经编写了测试程序来检查BeginInvoke的行为,我在这里写的所有内容都是这些测试的结果

    现在,为了扩展第二点,我们首先需要了解调度员的作用。显然这是一个非常简化的解释。

    任何Windows UI都通过处理消息来工作;例如,当用户将鼠标移到窗口上时,系统将向该窗口发送WM_MOUSEMOVE消息。

    系统通过添加队列发送消息,每个线程可能有一个队列,同一个线程创建的所有窗口共享同一个队列。

    在每个Windows程序的核心都有一个名为“消息循环”或“消息泵”的循环,该循环从队列中读取下一条消息并调用相应窗口的代码来处理该消息。

    在WPF中,此循环以及Dispatcher处理的所有相关处理。

    应用程序可以在消息循环中等待下一条消息,也可以执行某些操作。这就是为什么当你有一个很长的计算时,所有线程的窗口都没有响应 - 线程正忙着工作,并没有返回消息循环来处理下一条消息。

    Dispatcher.Invoke和BeginInvoke通过对请求的操作进行排队并在下次线程返回消息循环时执行它来工作。

    这就是为什么Dispatcher。(Begin)Invoke不能在你的方法中间“注入”代码,在方法返回之前你不会回到消息循环。

    BUT

    任何代码都可以运行消息循环。当你调用任何运行消息循环的东西时,将调用Dispatcher并运行(Begin)Invoke操作。

    什么类型的代码有消息循环?

    1. 任何具有GUI或接受用户输入的内容,例如对话框,消息框,拖放等等 - 如果那些没有消息循环,则应用程序将无响应且无法处理用户输入
    2. 在幕后使用Windows消息的进程间通信(大多数进程间通信方法,包括COM,使用它们)。
    3. 其他任何需要很长时间但不冻结系统的事情(系统未冻结的速度证明它正在处理消息)。
    4. 所以,总结一下:

      • Dispatcher不能只将代码拖放到您的线程中,它只能在应用程序处于“消息循环”时执行代码。
      • 您编写的任何代码都没有消息循环,除非您明确地编写它们。
      • 大多数UI代码没有自己的消息循环,例如,如果您调用Window.Show然后进行一些长计算,窗口将仅在计算完成后返回并且方法返回(并且应用程序返回到消息循环并处理打开和绘制窗口所需的所有消息。)
      • 但是在返回之前与用户交互的任何代码(MessageBox.Show,Window.ShowDialog)都必须有一个消息循环。
      • 某些通信代码(网络和进程间)使用消息循环,有些则不然,具体取决于您使用的具体实现。

答案 1 :(得分:0)

这是一个有趣的问题。 在调度程序中执行的项目在与UI交互相同的线程上排队并执行。这是关于这个主题的最好的文章: http://msdn.microsoft.com/en-us/library/ms741870.aspx

如果我冒险猜测,我会说 Dispatcher.Invoke(Action)可能会将一个原子工作项排队,所以它可能会好起来,但我不确定是否它将您的UI事件处理程序包装在原子操作项中,例如:

//Are these bits atomic?  Not sure.
upload.Cancel();
_cancelled = true;

为了安全起见,我会亲自锁定,但你的问题需要更多的研究。可能需要在反射器中潜水才能确定。

顺便说一句,我可能会优化你的锁定。

Dispatcher.Invoke(() => 
{
     if (!cancelled)
     {
          lock(myLock)
          {
               if(!cancelled)
                    view.Progress = e.Value;
          }
     }
}

但那可能有点过分了:))