c# - 使用 STA 时表单表现异常,线程耗时过长

标签 c# .net multithreading winforms sta

我们正在用 C# 开发一个多线程游戏引擎,我们遇到了一个问题,我们需要 STAThread 属性(或手动将我们的线程设置为 STA)来启用拖放支持(AllowDrop 不能设置没有STA)。但是,当我们启用 STA 并且更新方法比绘制方法花费的时间更长(如下所示)时,窗口不再正常运行 - 当它在任务栏中单击时,它不会像您期望的那样最小化和最大化它。确切的行为在不同的系统上是不同的,我猜这里会出现某种竞争条件。

这是我们的测试代码:

    [STAThread]
    public static void Main()
    {
        Form form = new Form();
        form.Show();

        Barrier barrier = new Barrier(2);

        Thread updateThread = new Thread(() => {
            while (true)
            {
                barrier.SignalAndWait();
                Thread.Sleep(30); //Update
                barrier.SignalAndWait();
            }
        });
        updateThread.Start();

        while (true)
        {
            barrier.SignalAndWait();
            Thread.Sleep(15); //Draw
            barrier.SignalAndWait();
            Application.DoEvents();
        }
    }

最佳答案

我认识到这个问题,我在 Vista 时代的一个非托管应用程序中调试了一个非常相似的问题。这个问题非常模糊,与您通过单击任务栏按钮生成的非常喜怒无常的 WM_ACTIVATEAPP 消息有关。特别是当它由嵌套消息循环分派(dispatch)并且该循环进行消息过滤以仅允许分派(dispatch)某些“安全”消息时。

这是由 Barrier.SignalAndWait() 调用引起的。这会阻塞 UI 线程并且对于 STA 线程来说是非法的。 CLR 对此做了一些事情,它在底层同步对象未发出信号时泵出自己的消息循环。如果是 那个 循环调度 WM_ACTIVATEAPP,并且可能性非常高,因为您阻塞了 30 毫秒并且只泵了一次,那么它就会出错。由于某些神秘的原因,后续消息不会被发送。几乎可以肯定是由该消息循环完成的过滤引起的。否则很难看到,该代码从未发布过,也无法反编译。

它是可以修复的,但并不容易。解决方法是强制 CLR 抽取此消息循环。这没关系,因为您的等待时间很短。您可以通过覆盖 SynchronizationContext.Wait() 方法来执行某些操作。不幸的是,这很难做到,WindowsFormsSynchronizationContext 类是密封的,因此无法从中派生以覆盖其 Wait() 方法。您需要访问 Reference Source并将该类复制/粘贴到您的代码中。给它另一个名字。

我将给出它的简短版本,显示您需要更改的内容:

using System.Runtime.InteropServices;

class MySynchronizationContext : SynchronizationContext {
    public MySynchronizationContext() {
        base.SetWaitNotificationRequired();
    }
    public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) {
        return WaitForMultipleObjects(waitHandles.Length, waitHandles, false, millisecondsTimeout);
    }

    [DllImport("kernel32.dll")]
    static extern int WaitForMultipleObjects(int nCount, IntPtr[] lpHandles,
        bool bWaitAll, int dwMilliseconds);
}

并安装新的同步提供程序:

    System.ComponentModel.AsyncOperationManager.SynchronizationContext = 
       new MySynchronizationContext();

简而言之,SetWaitNotificationRequired() 调用告诉 CLR 它应该调用您的 Wait() 覆盖而不是使用它自己的 Wait() 实现。并且 Wait() 覆盖使用操作系统的阻塞等待而不以其他方式进行抽取。当我测试它并解决问题时效果很好。

关于c# - 使用 STA 时表单表现异常,线程耗时过长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42176612/

相关文章:

c# - 弹出窗口不关闭事件 Stayopen 设置为 False

c# - Json.NET - 自定义转换器 - 字符串到 Int

c# - 如何使用过滤器从 C# 中的 DataTable 中删除行?

c# - 没有第二种形式的多线程消息泵

c# - c# - 如何在任何有异常的情况下向线程发出停止信号

c# - SQL 语法错误;请查看与您的 MySQL 服务器对应的手册

java - 线程越多,QuickSort 的性能就越差

objective-c - 由于辅助线程尝试更新UI而导致应用程序崩溃

.net - .NET 的 JavaCC?

c# - 为整数类型调用 ToString 时是否涉及装箱?