We are hosting .NET 4.0 and lots of WinForms windows in a Delphi application.
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.
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
?
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.
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);
}
}
}
The output:
P1:
P2: System.Windows.Forms.WindowsFormsSynchronizationContext
P3: System.Threading.SynchronizationContext
Basically:
- Before the form has been opened,
SynchronizationContext.Current
isnull
. - In the Form.Load event,
SynchronizationContext.Current
is an instance ofWindowsFormsSynchronizationContext
- After the form has closed,
SynchronizationContext.Current
has been reset back toSystem.Threading.SynchronizationContext
We have tried various tricks to try to fool this code:
- 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
, notShowDialog
)
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);
}
Since the main message loop is done from Delphi, .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.
Wrap WindowsFormsSynchronizationContext in your context. Like:
And set it before any windows form call. In this case it will not be replaced:
Hope it will help.