| File: System\Windows\Forms\WindowsFormsSynchronizationContext.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; namespace System.Windows.Forms; /// <summary> /// Provides a synchronization context for the Windows Forms application model. /// </summary> public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable { private Control? _controlToSendTo; private WeakReference<Thread>? _destinationThread; // ThreadStatics won't get initialized per thread: easiest to just invert the value. [ThreadStatic] private static bool t_doNotAutoInstall; [ThreadStatic] private static bool t_inSyncContextInstallation; [ThreadStatic] private static SynchronizationContext? t_previousSyncContext; public WindowsFormsSynchronizationContext() { // Store the current thread to ensure it stays alive during an invoke. DestinationThread = Thread.CurrentThread; _controlToSendTo = Application.ThreadContext.FromCurrent().MarshallingControl; Debug.Assert(_controlToSendTo.IsHandleCreated, "Marshaling control should have created its handle in its ctor."); } private WindowsFormsSynchronizationContext(Control? marshalingControl, Thread? destinationThread) { _controlToSendTo = marshalingControl; DestinationThread = destinationThread; Debug.Assert( _controlToSendTo is null || _controlToSendTo.IsHandleCreated, "Marshaling control should have created its handle in its ctor."); } // Directly holding onto the Thread can prevent ThreadStatics from finalizing. private Thread? DestinationThread { get => _destinationThread?.TryGetTarget(out Thread? target) == true ? target : null; set { if (value is not null) { if (_destinationThread is null) { _destinationThread = new(value); } else { _destinationThread.SetTarget(value); } } } } public void Dispose() { if (_controlToSendTo is { } control) { if (!control.IsDisposed) { control.Dispose(); } _controlToSendTo = null; } } public override void Send(SendOrPostCallback d, object? state) { // We don't call Send internally, only Post Thread? destinationThread = DestinationThread; if (destinationThread is null || !destinationThread.IsAlive) { throw new InvalidAsynchronousStateException(SR.ThreadNoLongerValid); } _controlToSendTo?.Invoke(d, [state]); } public override void Post(SendOrPostCallback d, object? state) => _controlToSendTo?.BeginInvoke(d, [state]); public override SynchronizationContext CreateCopy() => new WindowsFormsSynchronizationContext(_controlToSendTo, DestinationThread); /// <summary> /// Gets or sets a value indicating whether the <see cref="WindowsFormsSynchronizationContext"/> is installed when /// a control is created. /// </summary> /// <value> /// <see langword="true"/> if the <see cref="WindowsFormsSynchronizationContext"/> is installed; otherwise, /// <see langword="false"/>. The default is <see langword="true"/>. /// </value> /// <remarks> /// <para> /// The <see cref="AutoInstall"/> property determines whether the <see cref="WindowsFormsSynchronizationContext"/> /// is installed when a control is created, or when a message loop is started. /// </para> /// </remarks> [EditorBrowsable(EditorBrowsableState.Advanced)] public static bool AutoInstall { get => !t_doNotAutoInstall; set => t_doNotAutoInstall = !value; } // Instantiate and install a WinForms op sync context, and save off the old one. internal static void InstallIfNeeded() { // Exit if we shouldn't auto-install, if we've already installed and we haven't uninstalled, // or if we're being called recursively (creating the WinForms async op sync context can create // a parking window control). if (!AutoInstall || t_inSyncContextInstallation) { return; } if (Current is null) { t_previousSyncContext = null; } if (t_previousSyncContext is not null) { return; } t_inSyncContextInstallation = true; try { SynchronizationContext currentContext = AsyncOperationManager.SynchronizationContext; // Make sure we either have no sync context or that we have one of type SynchronizationContext if (currentContext is null || currentContext.GetType() == typeof(SynchronizationContext)) { t_previousSyncContext = currentContext; AsyncOperationManager.SynchronizationContext = new WindowsFormsSynchronizationContext(); } } finally { t_inSyncContextInstallation = false; } } public static void Uninstall() => Uninstall(turnOffAutoInstall: true); internal static void Uninstall(bool turnOffAutoInstall) { if (AutoInstall && AsyncOperationManager.SynchronizationContext is WindowsFormsSynchronizationContext) { try { AsyncOperationManager.SynchronizationContext = t_previousSyncContext is null ? new SynchronizationContext() : t_previousSyncContext; } finally { t_previousSyncContext = null; } } if (turnOffAutoInstall) { AutoInstall = false; } } }