Rationale
[Scenario and API proposal updated to reflect the current eco system.]
A growing number of components in and for WinForms requires to asynchronously marshal an async method to run on the UI-Thread. These are for example APIs around WebView2, projected native Windows 10 and 11 APIs or async APIs from modern libraries for example for Semantic Kernel.
Other scenarios make it necessary to show a Form, a Popup or a MessageBox asynchronously, to be in alignment of other UI stacks like WinUI or .NET MAUI and share/adapt their ViewModel implementations for migration and modernization purposes.
One example where we need to use these APIs is when we need to implement a Login-Dialog in WinForms to authenticate a user on a WebAPI which represents the WinForms App's backend, and which uses Windows WebView2 control and MSALs ICustomWebUI interface for acquiring an authorization code asynchronously.
Since a modern architecture would it make necessary to call this Dialog via an UI-Service from a ViewModel, we would need to show this Form either with ShowAsync or ShowDialogAsync. Inside of the Form, when we would need to navigate to a specific URL and to honor the implementation of ICustomWebUI, we would need to call NavigateAsyncTo which would need to run a) asynchronously and b) on WinForms's UI Thread:
public partial class FormWebLogin : Form, ICustomWebUi
{
public FormWebLogin()
{
InitializeComponent();
InitializeBrowser();
}
/// <summary>
/// Implements the <see cref="ICustomWebUi.AcquireAuthorizationCodeAsync(Uri, Uri, CancellationToken)"/> method.
/// </summary>
public async Task<Uri> AcquireAuthorizationCodeAsync(
Uri authorizationUri,
Uri redirectUri,
CancellationToken cancellationToken)
{
_redirectUri = redirectUri;
// NavigateToAsync must be called asynchronously AND need to run on the UI thread.
return await InvokeAsync<Uri>(
() => NavigateToAsync(authorizationUri),
cancellationToken);
}
The following is a demo which utilizes InvokeAsync with the Periodic Timer, demos parallelized rendering into the WinForms's GDI+ Graphics surface and also shows off the new Windows title bar customizing (which is part of the Dark Mode features):

API Suggestion:
Note: We suggest for .NET 9 to implement the new APIs under the Experimental attribute, so we can collect and react to respective feedback, and then introduce the new APIs finally in .NET 10.
namespace System.Windows.Forms;
public static partial class TaskDialog
{
public static Task<TaskDialogButton> ShowDialogAsync(nint hwndOwner, TaskDialogPage page, TaskDialogStartupLocation startupLocation, CancellationToken cancellationToken);
public static Task<TaskDialogButton> ShowDialogAsync(IWin32Window win32Window, TaskDialogPage page, TaskDialogStartupLocation startupLocation, CancellationToken cancellationToken);
public static Task<TaskDialogButton> ShowDialogAsync(TaskDialogPage page);
public static Task<TaskDialogButton> ShowDialogAsync(TaskDialogPage page, CancellationToken cancellationToken);
public static Task<TaskDialogButton> ShowDialogAsync(TaskDialogPage page, TaskDialogStartupLocation startupLocation);
public static Task<TaskDialogButton> ShowDialogAsync(TaskDialogPage page, TaskDialogStartupLocation startupLocation, CancellationToken cancellationToken);
}
public partial class Form
{
public Task ShowAsync(IWin32Window? owner = null, CancellationToken cancellationToken = default);
public Task<DialogResult> ShowDialogAsync();
public Task<DialogResult> ShowDialogAsync(IWin32Window owner);
public Task<DialogResult> ShowDialogAsync(CancellationToken cancellationToken);
public Task<DialogResult> ShowDialogAsync(IWin32Window owner, CancellationToken cancellationToken);
}
public partial class Control
{
// Awaiting the execution of the UI-Thread-marshalled `Action` or `Func`.
// If `Func` returns `Task[<TResult>]` it is also awaited.
public Task InvokeAsync(Action action);
public Task InvokeAsync(Action action, CancellationToken cancellationToken);
public Task InvokeAsync(Func<Task> asyncFunc, CancellationToken cancellationToken);
public Task<T> InvokeAsync<T>(Func<Task<T>> asyncFunc, CancellationToken cancellationToken);
public Task<TResult> InvokeAsync<TResult>(Func<TResult> syncFunction);
public Task<TResult> InvokeAsync<TResult>(Func<TResult> syncFunction, CancellationToken cancellationToken);
}
Rationale
[Scenario and API proposal updated to reflect the current eco system.]
A growing number of components in and for WinForms requires to asynchronously marshal an async method to run on the UI-Thread. These are for example APIs around WebView2, projected native Windows 10 and 11 APIs or async APIs from modern libraries for example for Semantic Kernel.
Other scenarios make it necessary to show a Form, a Popup or a MessageBox asynchronously, to be in alignment of other UI stacks like WinUI or .NET MAUI and share/adapt their ViewModel implementations for migration and modernization purposes.
One example where we need to use these APIs is when we need to implement a Login-Dialog in WinForms to authenticate a user on a WebAPI which represents the WinForms App's backend, and which uses Windows
WebView2control and MSALsICustomWebUIinterface for acquiring an authorization code asynchronously.Since a modern architecture would it make necessary to call this Dialog via an UI-Service from a ViewModel, we would need to show this Form either with
ShowAsyncorShowDialogAsync. Inside of the Form, when we would need to navigate to a specific URL and to honor the implementation ofICustomWebUI, we would need to callNavigateAsyncTowhich would need to run a) asynchronously and b) on WinForms's UI Thread:The following is a demo which utilizes InvokeAsync with the Periodic Timer, demos parallelized rendering into the WinForms's GDI+ Graphics surface and also shows off the new Windows title bar customizing (which is part of the Dark Mode features):
API Suggestion:
Note: We suggest for .NET 9 to implement the new APIs under the
Experimentalattribute, so we can collect and react to respective feedback, and then introduce the new APIs finally in .NET 10.