托管.NET和WinForms,SynchronizationContexts复位时,.NET窗口回报的ShowDialog窗口、WinForms、NET、ShowDialog

2023-09-06 10:17:43 作者:春天结束了

我们在Delphi应用程序托管.NET 4.0和大量的WinForms窗口。

We are hosting .NET 4.0 and lots of WinForms windows in a Delphi application.

我们已经发现,每当我们德尔福最终调用的ShowDialog A NET的形式,窗体关闭时, SynchronizationContext.Current 被重置为 System.Threading.SynchronizationContext ,它使用线程池。

We have discovered that whenever we from Delphi end up calling ShowDialog on a .NET form, when the form closes, SynchronizationContext.Current is reset back to System.Threading.SynchronizationContext which uses the thread pool.

有没有办法让我们迫使这的没有的发生,或者诱骗code将其重置回 WindowsFormsSynchronizationContext 相反,短期增加了必要的code到的每次的调用的ShowDialog

Is there a way for us to force this to not happen, or trick the code to reset it back to a WindowsFormsSynchronizationContext instead, short of adding the necessary code to every call to ShowDialog?

我们一直在托管.NET有些年头了,但直到最近才开始做,涉及异步code工作和失败,我们打开第二个窗口。

We have been hosting .NET for some years now but have only recently started doing work involving asynchronous code and this fails on the second window we open.

您可以复制我们看到这个简单的程序是什么,只需创建一个新的WinForms项目,并粘贴此code到Program.cs的:

You can reproduce what we're seeing with this simple program, simply create a new WinForms project and paste this code into Program.cs:

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication4
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Debug.WriteLine("P1: " + SynchronizationContext.Current);

            using (var fm = new Form())
            {
                fm.Load += (s, e) => Debug.WriteLine("P2: " + SynchronizationContext.Current);
                fm.ShowDialog();
            }

            Debug.WriteLine("P3: " + SynchronizationContext.Current);
        }
    }
}

输出:

P1: 
P2: System.Windows.Forms.WindowsFormsSynchronizationContext
P3: System.Threading.SynchronizationContext

基本上是:

在之前的形式已被打开, SynchronizationContext.Current 。 在Form.Load情况下, SynchronizationContext.Current 是一个实例 WindowsFormsSynchronizationContext 在窗体已关闭, SynchronizationContext.Current 已恢复为 System.Threading.SynchronizationContext Before the form has been opened, SynchronizationContext.Current is null. In the Form.Load event, SynchronizationContext.Current is an instance of WindowsFormsSynchronizationContext After the form has closed, SynchronizationContext.Current has been reset back to System.Threading.SynchronizationContext

我们已经尝试了各种招数,试图欺骗这个code:

We have tried various tricks to try to fool this code:

构造一个隐藏的表单 构造一个隐藏的形式,使这个在失主的ShowDialog 构造一个可见的形式(与显示显示,而不是的ShowDialog ) Constructing a hidden form Constructing a hidden form and making this the owner in the call to ShowDialog Constructing a visible form (shown with Show, not ShowDialog)

在翻翻,我们认为我们已经确定了可能的原因,在这一行:Applicationcs#3445 并起:

After looking through the reference source we think we've identified the likely reason, at this line: Applicationcs#3445 and onwards:

messageLoopCount--;

if (messageLoopCount == 0) {
    // If last message loop shutting down, install the
    // previous op [....] context in place before we started the first
    // message loop.
    WindowsFormsSynchronizationContext.Uninstall(false);
}

由于主消息循环是从德尔福完成,.NET并不知道这一点,因此在第一个窗口弹出打开的意志,在关闭时,最终拆除了世界,因为.NET认为世界即将结束。

Since the main message loop is done from Delp .NET has no knowledge of this and thus the first window to pop open will, when closed, end up tearing down the world since .NET thinks the world is about to end.

推荐答案

裹WindowsFormsSynchronizationContext在你的环境。 像:

Wrap WindowsFormsSynchronizationContext in your context. Like:

class MySynchronizationContext : SynchronizationContext
{
    private SynchronizationContext context = new WindowsFormsSynchronizationContext();

    public override SynchronizationContext CreateCopy()
    {
        return context.CreateCopy();
    }

    public override bool Equals(object obj)
    {
        return context.Equals(obj);
    }

    public override int GetHashCode()
    {
        return context.GetHashCode();
    }

    public override void OperationCompleted()
    {
        context.OperationCompleted();
    }

    public override void OperationStarted()
    {
        context.OperationStarted();
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        context.Post(d, state);
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        context.Send(d, state);
    }

    public override string ToString()
    {
        return "Wrapped";
    }

    public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
    {
        return context.Wait(waitHandles, waitAll, millisecondsTimeout);
    }
}

和任何Windows窗体调用之前设置它。在这种情况下,它不会被替换:

And set it before any windows form call. In this case it will not be replaced:

        /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        MySynchronizationContext context = new MySynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(context);
        Debug.WriteLine("P1: " + SynchronizationContext.Current );

        using (var fm = new Form())
        {
            fm.Load += (s, e) => Debug.WriteLine("P2: " + SynchronizationContext.Current);
            fm.ShowDialog();
        }

        Debug.WriteLine("P3: " + SynchronizationContext.Current );
    }

希望这将有助于。

Hope it will help.