Skip to content

Commit dd55ce8

Browse files
committed
Primes UI Automation at STA startup so form teardown no longer triggers the first-time UIA activation that deadlocks the x86 host.
1 parent d7cd22d commit dd55ce8

1 file changed

Lines changed: 25 additions & 16 deletions

File tree

‎MapWinGisTests-net/MapWinGisTests/StaApartment.cs‎

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,37 @@ private void ThreadMain()
7676
}
7777

7878
/// <summary>
79-
/// Forces first-time UI Automation / accessibility initialization on this STA thread by
80-
/// creating a throwaway control, realizing its accessible object, and then destroying it. Doing
81-
/// this once at startup avoids the lazy UIA activation that otherwise deadlocks on the 32-bit
82-
/// host during form teardown (see <see cref="ThreadMain"/>).
79+
/// Forces first-time UI Automation initialization on this STA thread by creating a real
80+
/// window handle and then destroying it. Destroying the handle raises WM_DESTROY, which
81+
/// runs Control.ReleaseUiaProvider -> UiaReturnRawElementProvider -> UIAutomationCore
82+
/// CheckInit/DoInit -> CoCreateInstance(CUIAutomation7). Running that once here, while the
83+
/// STA thread is idle and the native OCX/loader work is not in flight, means later form
84+
/// teardown only releases an already-initialized UIA core instead of triggering a
85+
/// cross-apartment COM activation that deadlocks on the 32-bit (x86) host.
8386
/// </summary>
8487
private static void WarmUpUiAutomation()
8588
{
86-
try {
87-
using var warmup = new Control();
88-
// Force handle creation so the control has a real HWND.
89+
try {
90+
// Use a Form (not a bare Control): its WM_DESTROY path matches what Form1 hits
91+
// during real test teardown, so the same UIA init code runs now.
92+
using var warmup = new Form
93+
{
94+
ShowInTaskbar = false,
95+
FormBorderStyle = FormBorderStyle.None,
96+
StartPosition = FormStartPosition.Manual,
97+
Location = new System.Drawing.Point(-32000, -32000),
98+
Size = new System.Drawing.Size(1, 1),
99+
};
100+
101+
// Force native handle creation.
89102
_ = warmup.Handle;
90103

91-
// Touching AccessibilityObject realizes the control's accessible/UIA provider, which
92-
// drives the same UIAutomationCore initialization that Control.ReleaseUiaProvider hits
93-
// during WM_DESTROY. Doing it here, on an idle STA thread, primes that init safely so
94-
// later form teardown only releases an already-initialized provider.
95-
_ = warmup.AccessibilityObject;
96-
97-
// Disposing now runs the release path once, while the thread is idle rather than
98-
// mid-teardown.
104+
// Destroy the handle => WM_DESTROY => ReleaseUiaProvider => UIAutomationCore DoInit.
105+
// This is the exact path that deadlocks at teardown; doing it here primes the UIA
106+
// core once, on an idle thread.
107+
warmup.Dispose();
99108
} catch {
100-
// Warm-up is best-effort; it must not fail apartment startup.
109+
// Warm-up is best-effort; it must never fail apartment startup.
101110
}
102111
}
103112

0 commit comments

Comments
 (0)