并在UI线程进入一个锁为何引发OnPaint事件? [英] Why did entering a lock on a UI thread trigger an OnPaint event?

查看:184
本文介绍了并在UI线程进入一个锁为何引发OnPaint事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我碰到的东西,我根本看不懂。
在我的应用我有几个线程所有添加(和删除)项目共享集合(使用共享锁)。
UI线程使用一个计时器,并在每一个剔它使用的收集更新其UI。

I came across something I simply don't understand. In my application I have several threads all adding (and removing) items to a shared collection (using a shared lock). The UI thread uses a timer, and on every tick it uses the collection to update its UI.

由于我们不希望UI线程持有到锁定很长一段时间,并阻止其他线程,我们这样做的方式是,首先我们获得锁,我们复制集合,我们解除锁定,然后我们的副本。
的代码看起来是这样的:

Since we don't want the UI thread to hold on to the lock for a long time and block the other threads, the way we do it, is that first we acquire the lock, we copy the collection, we release the lock and then work on our copy. The code looks like this:

public void GUIRefresh()
{
    ///...
    List<Item> tmpList;
    lock (Locker)
    {
         tmpList = SharedList.ToList();
    }
    // Update the datagrid using the tmp list.
}



虽然它工作得很好,我们注意到,有时也有在应用速度变慢,当我们设法抓住一个堆栈跟踪,我们看到了这一点:

While it works fine, we noticed that sometimes there are slowdowns in the application, and when we managed to catch a stacktrace, we saw this:

....
at System.Windows.Forms.DataGrid.OnPaint(PaintEventArgs pe)
at MyDataGrid.OnPaint(PaintEventArgs pe)
at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Threading.Monitor.Enter(Object obj)
at MyApplication.GuiRefresh()   
at System.Windows.Forms.Timer.OnTick(EventArgs e)
at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
....

注意,进入锁(Monitor.Enter)之后的NativeWindow。回拨导致的OnPaint。

Note that entering the lock (Monitor.Enter) is followed by NativeWindow.Callback which leads to OnPaint.


  • 这怎么可能?请问UI线程被劫持来检查它的消息泵?那有意义吗?或者是有什么人在这里?

  • How is that possible? Does the UI thread gets hijacked to check its message pump? Does that make sense? Or is there something else here?

有没有办法避免呢?我不希望的OnPaint从锁内调用。

Is there a way to avoid it? I don't want the OnPaint to be called from within the lock.

感谢。

推荐答案

一个GUI应用程序的主线程是STA线程,单线程单元。注意你的程序的main()方法[STAThread]属性。 STA是一个COM术语,它提供了热情好客的家是根本线程安全的组成部分,使他们能够从一个工作线程调用。 COM仍然在.NET应用程序非常活跃。拖放,剪贴板,外壳对话框像打开文件对话框和通用控件就像web浏览器都是单线程的COM对象。 STA是UI线程硬性要求。

The main thread of a GUI app is an STA thread, Single Threaded Apartment. Note the [STAThread] attribute on the Main() method of your program. STA is a COM term, it gives a hospitable home to components that are fundamentally thread-unsafe, allowing them to be called from a worker thread. COM is still very much alive in .NET apps. Drag and drop, the Clipboard, the shell dialogs like OpenFileDialog and common controls like WebBrowser are all single threaded COM objects. STA is a hard requirement for UI threads.

有关STA线程的行为的合同是它必须泵消息循环,并且不允许阻止。阻塞是很容易造成僵局,因为它不允许这些单元线程COM组件封送处理的进展。你是阻止你的锁定的语句中的主题。

The behavioral contract for an STA thread is that it must pump a message loop and is not allowed to block. Blocking is very likely to cause deadlock since it doesn't allow the marshaling for these apartment threaded COM components to progress. You are blocking the thread with your lock statement.

在CLR是该需求非常清楚并执行关于它的东西。阻塞调用像Monitor.Enter(),WaitHandle.WaitOne /任何()或的Thread.join()泵消息循环。 ,做的那种原生的Windows API的是的 MsgWaitForMultipleObjects()。的那个消息循环分派Windows消息保持STA活着,包括油漆消息。这可能会导致课程的重入问题,画图不应该是一个问题。

The CLR is very much aware of that requirement and does something about it. Blocking calls like Monitor.Enter(), WaitHandle.WaitOne/Any() or Thread.Join() pump a message loop. The kind of native Windows API that does that is MsgWaitForMultipleObjects(). That message loop dispatches Windows messages to keep the STA alive, including paint messages. This can cause re-entrancy problems of course, Paint should not be a problem.

有关于这个良好的背景资料信息在这的克里斯Brumme博客文章

There's good backgrounder info on this in this Chris Brumme blog post.

也许这一切听起来很耳熟,你可能无法帮助注意到这听起来很像一个应用程序调用Application.DoEvents()。可能是目​​前唯一最可怕的方法来解决UI的冻结问题。这对引擎盖下会发生什么相当精确的心智模型,的DoEvents()也泵消息循环。唯一的区别是,CLR的相当于是一个比较挑剔什么样的信息,它允许被分派,它过滤他们。不同的DoEvents()的调度一切。不幸的是既不Brumme的职位也不是SSCLI20源足够详细,确切地知道什么是越来越出动,这是否实际的CLR函数是不是在源提供过于庞大反编译。但很明显,你可以看到,它的的过滤器WM_PAINT。它会过滤真正的麻烦制造者,输入事件通知一样,允许用户关闭窗口或点击按钮的那种。

Maybe this all rings a bell, you probably can't help notice that this sounds a lot like an app calling Application.DoEvents(). Probably the single-most dreaded method available to solve UI freezing problems. That's a pretty accurate mental model for what happens under the hood, DoEvents() also pumps the message loop. The only difference is that the CLR's equivalent is a bit more selective about what messages it allows to be dispatched, it filters them. Unlike DoEvents() which dispatches everything. Unfortunately neither Brumme's post nor the SSCLI20 source is sufficiently detailed to know exactly what is getting dispatched, the actual CLR function that does this is not available in source and far too large to decompile. But clearly you can see that it does not filter WM_PAINT. It will filter the real trouble-makers, input event notifications like the kind that allows the user to close a window or click a button.

功能,不是一个错误。通过去除阻塞和依靠编组回调避免重入头痛。 BackgroundWorker.RunWorkerCompleted是一个典型的例子。

Feature, not a bug. Avoid re-entrancy headaches by removing the blocking and relying on marshaled callbacks. BackgroundWorker.RunWorkerCompleted is a classic example.

这篇关于并在UI线程进入一个锁为何引发OnPaint事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆