Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/288.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么在UI线程上输入锁会触发OnPaint事件?_C#_Winforms_Multithreading - Fatal编程技术网

C# 为什么在UI线程上输入锁会触发OnPaint事件?

C# 为什么在UI线程上输入锁会触发OnPaint事件?,c#,winforms,multithreading,C#,Winforms,Multithreading,我遇到了一些我根本不明白的事情。 在我的应用程序中,我有几个线程,所有线程都向共享集合添加(和删除)项(使用共享锁)。 UI线程使用计时器,并且在每次勾选时都使用集合更新其UI 由于我们不希望UI线程长时间持有锁并阻止其他线程,因此我们的做法是,首先获取锁,复制集合,释放锁,然后处理副本。 代码如下所示: public void GUIRefresh() { ///... List<Item> tmpList; lock (Locker) {

我遇到了一些我根本不明白的事情。 在我的应用程序中,我有几个线程,所有线程都向共享集合添加(和删除)项(使用共享锁)。 UI线程使用计时器,并且在每次勾选时都使用集合更新其UI

由于我们不希望UI线程长时间持有锁并阻止其他线程,因此我们的做法是,首先获取锁,复制集合,释放锁,然后处理副本。 代码如下所示:

public void GUIRefresh()
{
    ///...
    List<Item> tmpList;
    lock (Locker)
    {
         tmpList = SharedList.ToList();
    }
    // Update the datagrid using the tmp list.
}
请注意,输入锁(Monitor.Enter)之后是NativeWindow.Callback,这将导致OnPaint

  • 这怎么可能?UI线程是否被劫持以检查其消息泵?这有意义吗?或者这里还有别的东西吗

  • 有办法避免吗?我不想从锁内调用OnPaint

谢谢。

问得好

NET中的所有等待都是“可警报的”。这意味着,如果等待阻塞,Windows可以在等待堆栈的顶部运行“异步过程调用”。这可能包括处理一些windows消息。我没有特别尝试过WM_绘画,但根据你的观察,我猜它包括在内

一些MSDN链接:


Joe Duffy的书《Windows上的并发编程》也介绍了这一点。

GUI应用程序的主线程是一个STA线程、单线程单元。注意程序Main()方法上的[StatThread]属性。STA是一个COM术语,它为基本上线程不安全的组件提供了一个好客的家,允许从工作线程调用它们。COM在.NET应用程序中仍然非常活跃。拖放、剪贴板、外壳对话框(如OpenFileDialog)和常见控件(如WebBrowser)都是单线程COM对象。STA是UI线程的硬需求

STA线程的行为约定是,它必须泵送消息循环,并且不允许阻塞。阻塞很可能导致死锁,因为它不允许对这些单元线程COM组件进行封送处理。您正在使用lock语句阻塞线程

CLR非常了解这一要求,并为此做了一些事情。阻止诸如Monitor.Enter()、WaitHandle.WaitOne/Any()或Thread.Join()之类的调用以泵送消息循环。实现这一点的本机Windows API是消息循环分派Windows消息以使STA保持活动状态,包括绘制消息。这会导致重入问题当然,油漆不应该是一个问题

这里面有很好的背景资料

也许这一切都敲响了警钟,你可能会情不自禁地注意到这听起来很像一个调用Application.DoEvents()的应用程序。可能是解决UI冻结问题最可怕的方法。这是一个相当准确的心理模型,它描述了引擎盖下发生的事情,DoEvents()也会推动消息循环。唯一的区别是,CLR的等价物在允许发送哪些消息方面更具选择性,它会过滤这些消息。与DoEvents()不同,DoEvents()发送所有内容。不幸的是,无论是Brumme的帖子还是SSCLI20源代码都没有足够的详细信息来准确地知道要调度什么,实际的CLR函数在源代码中不可用,而且太大,无法反编译。但很明显,你可以看到它不会过滤WM_油漆。它将过滤真正的麻烦制造者,输入事件通知,比如允许用户关闭窗口或单击按钮的通知


功能,而不是bug。通过移除阻塞和依赖封送回调,避免重新进入的麻烦。BackgroundWorker.RunWorkerCompleted是一个典型的例子。

我在等待句柄阻塞时发现了这个问题。对此的回答给了我下一步实施的提示:

 public static class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern UInt32 WaitForSingleObject(SafeWaitHandle hHandle, UInt32 dwMilliseconds);
}

public static class WaitHandleExtensions
{
    const UInt32 INFINITE = 0xFFFFFFFF;
    const UInt32 WAIT_ABANDONED = 0x00000080;
    const UInt32 WAIT_OBJECT_0 = 0x00000000;
    const UInt32 WAIT_TIMEOUT = 0x00000102;
    const UInt32 WAIT_FAILED = INFINITE;

    /// <summary>
    /// Waits preventing an I/O completion routine or an APC for execution by the waiting thread (unlike default `alertable`  .NET wait). E.g. prevents STA message pump in background. 
    /// </summary>
    /// <returns></returns>
    /// <seealso cref="http://stackoverflow.com/questions/8431221/why-did-entering-a-lock-on-a-ui-thread-trigger-an-onpaint-event">
    /// Why did entering a lock on a UI thread trigger an OnPaint event?
    /// </seealso>
    public static bool WaitOneNonAlertable(this WaitHandle current, int millisecondsTimeout)
    {
        if (millisecondsTimeout < -1)
            throw new ArgumentOutOfRangeException("millisecondsTimeout", millisecondsTimeout, "Bad wait timeout");
        uint ret = NativeMethods.WaitForSingleObject(current.SafeWaitHandle, (UInt32)millisecondsTimeout);
        switch (ret)
        {
            case WAIT_OBJECT_0:
                return true;
            case WAIT_TIMEOUT:
                return false;
            case WAIT_ABANDONED:
                throw new AbandonedMutexException();
            case WAIT_FAILED:
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            default:
                return false;
        }
    }
}
公共静态类NativeMethods
{
[DllImport(“kernel32.dll”,SetLastError=true)]
内部静态外部UInt32 WaitForSingleObject(SafeWaitHandle hHandle,UInt32 dw毫秒);
}
公共静态类WaitHandleExtensions
{
常数UInt32无穷=0xFFFFFFFF;
const UInt32 WAIT_放弃=0x00000080;
const UInt32 WAIT_OBJECT_0=0x00000000;
const UInt32 WAIT_TIMEOUT=0x00000102;
const UInt32 WAIT_FAILED=无限;
/// 
///等待阻止I/O完成例程或APC由等待线程执行(与默认的'alertable`.NET wait不同)。例如,在后台阻止STA消息泵。
/// 
/// 
/// 
///为什么在UI线程上输入锁会触发OnPaint事件?
/// 
公共静态bool WaitOneNonAlertable(此WaitHandle当前值,int毫秒)
{
如果(毫秒刺激<-1)
抛出新的ArgumentOutOfRangeException(“毫秒计时”,毫秒计时,“错误等待超时”);
uint-ret=NativeMethods.WaitForSingleObject(current.SafeWaitHandle,(UInt32)毫秒计时);
开关(ret)
{
案例等待对象0:
返回true;
案例等待超时:
返回false;
案件被放弃:
抛出新的废弃MutexException();
案例等待失败:
抛出新的System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
违约:
返回false;
}
}
}

如何捕获堆栈跟踪?我们持有对线程的引用。然后我们执行thread.Suspend();日志(新的StackTrace(thread,true).ToString());thread.Resume();在VisualStudio中调试应用程序时,我得到了类似的stacktrace。当DataGridView开始非常缓慢地绘制时,我刚刚点击了暂停(这样你就可以看到每个单元格突然被绘制),并注意到它正在从一个
锁处理
OnPaint
(我的stacktrace显示“ma
 public static class NativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern UInt32 WaitForSingleObject(SafeWaitHandle hHandle, UInt32 dwMilliseconds);
}

public static class WaitHandleExtensions
{
    const UInt32 INFINITE = 0xFFFFFFFF;
    const UInt32 WAIT_ABANDONED = 0x00000080;
    const UInt32 WAIT_OBJECT_0 = 0x00000000;
    const UInt32 WAIT_TIMEOUT = 0x00000102;
    const UInt32 WAIT_FAILED = INFINITE;

    /// <summary>
    /// Waits preventing an I/O completion routine or an APC for execution by the waiting thread (unlike default `alertable`  .NET wait). E.g. prevents STA message pump in background. 
    /// </summary>
    /// <returns></returns>
    /// <seealso cref="http://stackoverflow.com/questions/8431221/why-did-entering-a-lock-on-a-ui-thread-trigger-an-onpaint-event">
    /// Why did entering a lock on a UI thread trigger an OnPaint event?
    /// </seealso>
    public static bool WaitOneNonAlertable(this WaitHandle current, int millisecondsTimeout)
    {
        if (millisecondsTimeout < -1)
            throw new ArgumentOutOfRangeException("millisecondsTimeout", millisecondsTimeout, "Bad wait timeout");
        uint ret = NativeMethods.WaitForSingleObject(current.SafeWaitHandle, (UInt32)millisecondsTimeout);
        switch (ret)
        {
            case WAIT_OBJECT_0:
                return true;
            case WAIT_TIMEOUT:
                return false;
            case WAIT_ABANDONED:
                throw new AbandonedMutexException();
            case WAIT_FAILED:
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            default:
                return false;
        }
    }
}