简体   繁体   English

使用dispatcher.Invoke是否会使我的线程安全?

[英]Does using dispatcher.Invoke make my thread safe?

In my WPF app I have a long running upload running, which raises progress events as it goes which updates a progress bar. 在我的WPF应用程序中,我运行了长时间运行的上载程序,它会在运行过程中引发进度事件,从而更新进度条。 The user also has a chance of cancelling the upload, or it might go wrong. 用户还可以取消上传,否则可能会出错。 These are all async events, so they need to be executed using Dispatcher.Invoke in order to update the UI. 这些都是异步事件,因此需要使用Dispatcher.Invoke执行它们以更新UI。

So the code looks like this, ish: 因此,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;
    }
}

Supposing that setting view.Progress on a disposed view is going to throw an error, is this code thread safe? 假设设置视图。在已配置视图上的进度将引发错误,此代码线程安全吗? ie if a user clicks cancel while the progress is updating, he/she will have to wait until the progress has been updated, and if the progress is updated during execution of OnCancelButtonClicked, the Dispatcher.Invoke call will cause the view.Progress update to be queued til after _cancelled is set, so I won't get a problem there. 例如,如果用户在进度更新时单击“取消”,则他/她将不得不等到进度已更新,并且如果在执行OnCancelButtonClicked期间更新了进度,则Dispatcher.Invoke调用将导致view.Progress更新为设置_cancelled之后直到被排队,所以在那里我不会有问题。

Or do I need a lock to be safe, a 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;
        }
    }
}

You don't have to add a lock. 您不必添加锁。 Dispatcher.Invoke and BeginInvoke requests will not run in the middle of other code (that's the whole point of them). Dispatcher.Invoke和BeginInvoke请求将不会在其他代码中间运行(这是它们的全部重点)。

Just two things to consider: 仅需考虑两件事:

  1. BeginInvoke may be more appropriate in this case, Invoke will queue the request and then block the calling thread until the UI thread becomes idle and finishes executing the code, BeginInvoke will only queue the request without blocking. 在这种情况下,BeginInvoke可能更合适,Invoke会将请求排队,然后阻塞调用线程,直到UI线程变为空闲并完成代码执行为止,BeginInvoke只会将请求排队而不会阻塞。
  2. Some operations, especially operations that open windows (including message boxes) or do inter-process communication may allow the queued dispatcher operations to run. 某些操作,特别是打开窗口(包括消息框)或进行进程间通信的操作,可能允许排队的调度程序运行。

EDIT: first, I don't have citations because the MSDN pages on the subject are unfortunately very low on details - but I have written test programs to check the behavior of BeginInvoke and everything I write here is the result of those tests. 编辑:首先,我没有被引用,因为不幸的是,关于该主题的MSDN页面的详细信息很低-但是我编写了测试程序来检查BeginInvoke的行为,我在这里编写的所有内容都是这些测试的结果。

Now, to expand on the second point we first need to understand what the dispatcher does. 现在,为了扩展第二点,我们首先需要了解调度程序的功能。 Obviously this is a very simplified explanation. 显然,这是一个非常简化的解释。

Any Windows UI works by processing messages; 任何Windows UI都可以通过处理消息来工作。 For example when the user moves the mouse over a window the system will send that window a WM_MOUSEMOVE message. 例如,当用户将鼠标移到窗口上时,系统将向该窗口发送WM_MOUSEMOVE消息。

The system send the message by adding it a queue, each thread may have a queue, all windows created by the same thread share the same queue. 系统通过将消息添加到队列中来发送消息,每个线程可能有一个队列,同一线程创建的所有窗口共享同一队列。

In the heart of every Windows program there's a loop called "message loop" or "message pump", this loop reads the next message from the queue and calls the appropriate window's code to process that message. 在每个Windows程序的心脏中,都有一个称为“消息循环”或“消息泵”的循环,该循环从队列中读取下一条消息,并调用适当的窗口代码来处理该消息。

In WPF this loop and all the related processing handled by the Dispatcher. 在WPF中,此循环以及分派器处理的所有相关处理。

An application can either be in the message loop waiting for the next message or it could be doing something. 应用程序可以在消息循环中等待下一条消息,也可以在执行某些操作。 That is why when you have a long calculation all the thread's windows become unresponsive - the thread is busy working and doesn't return to the message loop to process the next message. 这就是为什么当您进行长时间的计算时,所有线程的窗口都不响应-线程正在忙于工作,并且不会返回到消息循环来处理下一条消息。

Dispatcher.Invoke and BeginInvoke works by queuing the requested operation and executing it the next time the thread returns to the message loop. Dispatcher.Invoke和BeginInvoke的工作方式是使请求的操作排队并在线程下次返回消息循环时执行该操作。

That is why Dispatcher.(Begin)Invoke can't "inject" code in the middle of your method, you won't get back to the message loop until your method returns. 这就是为什么Dispatcher。(Begin)Invoke不能在方法中间“注入”代码的原因,直到方法返回之前,您不会回到消息循环。

BUT

Any code can run a message loop. 任何代码都可以运行消息循环。 When you call anything that runs a message loop the Dispatcher will be called and can run the (Begin)Invoke operations. 当您调用运行消息循环的任何程序时,将调用Dispatcher并可以运行(Begin)Invoke操作。

What kinds of code has a message loop? 哪种代码具有消息循环?

  1. Anything that has a GUI or that accepts user input, for example dialog boxes, message boxes, drag&drop etc. - if those didn't have a message loop then the app would have been unresponsive and unable to handle user input. 具有GUI或接受用户输入的任何内容,例如对话框,消息框,拖放等-如果这些窗口没有消息循环,则该应用程序将无响应并且无法处理用户输入。
  2. Inter-process communication that uses windows messages behind the scenes (most inter-process communication methods, including COM, use them). 使用幕后消息的进程间通信(大多数进程间通信方法(包括COM)都使用它们)。
  3. Anything else that takes a long time and doesn't freeze the system (the fast that the system isn't frozen is proof it's processing messages). 其他所有需要很长时间且不会冻结系统的事情(系统未冻结的速度很快就证明了它正在处理消息)。

So, to summarize: 因此,总结一下:

  • the Dispatcher can't just drop code into your thread, it can only execute code when the application is in the "message loop". Dispatcher不能只将代码放入线程中,而只能在应用程序处于“消息循环”中时执行代码。
  • Any code you write doesn't have message loops unless you explicitly wrote them. 您编写的任何代码都没有消息循环,除非您明确地编写了它们。
  • Most UI code doesn't have it's own message loop, for example if you call Window.Show and then do some long calculation the window will only appear after the calculation is finished and the method returns (and the app returns to the message loop and processes all the messages required to open and draw a window). 大多数用户界面代码都没有自己的消息循环,例如,如果您调用Window.Show然后进行一些长时间的计算,则只有在计算完成并且方法返回后,窗口才会出现(应用程序返回到消息循环并处理打开和绘制窗口所需的所有消息)。
  • But any code that interacts with the user before it returns (MessageBox.Show, Window.ShowDialog) has to have a message loop. 但是,任何在返回之前与用户交互的代码(MessageBox.Show,Window.ShowDialog)都必须具有消息循环。
  • Some communication code (network and inter-process) uses message loops, some doesn't, depending on the specific implementation you are using. 某些通信代码(网络和进程间)使用消息循环,而某些则不使用,这取决于您使用的特定实现。

This is an interesting question. 这是个有趣的问题。 Items executed in the dispatcher are queued and execute on the same thread as UI interaction. 在调度程序中执行的项目被排队,并在与 UI交互相同的线程上执行 Here's the best article on this subject: http://msdn.microsoft.com/en-us/library/ms741870.aspx 这是关于此主题的最佳文章: http : //msdn.microsoft.com/zh-cn/library/ms741870.aspx

If I were to venture a guess, I would say that Dispatcher.Invoke(Action) probably queues an atomic work item , so it's likely going to be ok, however I'm not sure if it wraps up your UI event handler in an atomic action item for instance: 如果我冒险猜测,我会说Dispatcher.Invoke(Action)可能会排队一个原子工作项 ,因此可能没问题,但是我不确定它是否将您的UI事件处理程序包装在原子中动作项目,例如:

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

For safety's sake I would personally lock, but your question warrants more research. 为了安全起见,我会亲自锁定,但是您的问题值得进一步研究。 Might need a dive in reflector to figure out for sure. 可能需要潜水反射器才能确定。

As an aside, I'd probably optimize your lock a bit. 顺便说一句,我可能会优化您的锁。

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

But that's probably overkill :) 但这可能是矫kill过正:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM