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

mci*_*321 1 wpf dispatcher thread-safety

在我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

假设在处理视图上设置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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Nir*_*Nir 8

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

只需要考虑两件事:

  1. 在这种情况下,BeginInvoke可能更合适,Invoke将对请求进行排队,然后阻止调用线程,直到UI线程变为空闲并完成代码执行,BeginInvoke将仅阻塞请求.
  2. 某些操作,尤其是打开窗口(包括消息框)或进行进程间通信的操作可能允许排队的调度程序操作运行.

编辑:首先,我没有引用,因为关于这个问题的MSDN页面的细节很少 - 但我已经编写了测试程序来检查BeginInvoke的行为,我写的所有内容都是这些测试的结果.

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

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

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

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

在WPF中,此循环和Dispatcher处理的所有相关处理.

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

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

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

任何代码都可以运行消息循环.当您调用任何运行消息循环的内容时,将调用Dispatcher并运行(Begin)Invoke操作.

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

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

所以,总结一下:

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