diff --git a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt index bd30f077024..abe76f5f290 100644 --- a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt @@ -6,7 +6,7 @@ static System.Windows.Forms.TaskDialog.ShowDialogAsync(nint hwndOwner, System.Wi static System.Windows.Forms.TaskDialog.ShowDialogAsync(nint hwndOwner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation) -> System.Threading.Tasks.Task! static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page) -> System.Threading.Tasks.Task! static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation) -> System.Threading.Tasks.Task! -System.Windows.Forms.Control.InvokeAsync(System.Action! action, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +System.Windows.Forms.Control.InvokeAsync(System.Action! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! System.Windows.Forms.Control.InvokeAsync(System.Func! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! System.Windows.Forms.Control.InvokeAsync(System.Func>! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! System.Windows.Forms.Control.InvokeAsync(System.Func! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control_InvokeAsync.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control_InvokeAsync.cs index 40dfe6680cc..125774ee716 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control_InvokeAsync.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control_InvokeAsync.cs @@ -1,24 +1,37 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Windows.Forms.Analyzers.Diagnostics; - namespace System.Windows.Forms; public partial class Control { /// - /// Invokes the specified synchronous function asynchronously on the thread that owns the control's handle. + /// Invokes the specified synchronous callback asynchronously on the thread that owns the control's handle. /// - /// The synchronous action to execute. + /// The synchronous action to execute. /// The cancellation token. - /// A task representing the operation and containing the function's result. - [Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")] + /// A task representing the operation. + /// + /// + /// Note: When you pass a to this method, the method will return, + /// but the callback will still be executed. The callback will be running on the UI thread and will be + /// also blocking the UI thread. InvokeAsync in this case is just queuing the callback to the end of the + /// message queue and returns immediately, but as soon as the callback gets executed, it will still block + /// the UI thread for the time it is running. For this reason, it is recommended to only execute short sync running + /// operations in the callback, like updating a control's property or similar. + /// + /// + /// If you want to execute a long-running operation, consider using asynchronous callbacks instead, + /// by making sure that you use either the overload + /// or + /// . + /// + /// #pragma warning disable RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads - public async Task InvokeAsync(Action action, CancellationToken cancellationToken = default) + public async Task InvokeAsync(Action callback, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads { - ArgumentNullException.ThrowIfNull(action); + ArgumentNullException.ThrowIfNull(callback); if (cancellationToken.IsCancellationRequested) return; @@ -41,7 +54,7 @@ void WrappedAction() return; } - action(); + callback(); tcs.TrySetResult(); } catch (Exception ex) @@ -52,13 +65,34 @@ void WrappedAction() } /// - /// Invokes the specified synchronous function asynchronously on the thread that owns the control's handle. + /// Invokes the specified synchronous callback asynchronously on the thread that owns the control's handle. /// - /// The return type of the synchronous function. + /// The return type of the synchronous callback. /// The synchronous function to execute. /// The cancellation token. /// A task representing the operation and containing the function's result. - [Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")] + /// + /// + /// Note: When you pass a to this method, the method will return, + /// but the callback will still be executed. The callback will be running on the UI thread and will be + /// also blocking the UI thread. InvokeAsync in this case is just queuing the callback to the end of the + /// message queue and returns immediately, but as soon as the callback is executed, it will still block + /// the UI for the time it is running. For this reason, it is recommended to only execute short sync running + /// operations in the callback, like updating a control's property or similar. + /// + /// + /// If you want to execute a long-running operation, consider using asynchronous callbacks instead, which you use + /// with the overloads of InvokeAsync described below. + /// + /// + /// Important: Also note that if you use this overload to pass a callback which returns a + /// that this Task will NOT be awaited but return immediately and has the characteristics of an + /// "engage-and-forget". If you want the task which you pass to be awaited, make sure that you + /// use either the overload + /// or + /// . + /// + /// #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public async Task InvokeAsync(Func callback, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters @@ -74,11 +108,11 @@ public async Task InvokeAsync(Func callback, CancellationToken cancella using (cancellationToken.Register(() => tcs.SetCanceled(), useSynchronizationContext: false)) { - BeginInvoke(WrappedFunction); + BeginInvoke(WrappedCallback); return await tcs.Task.ConfigureAwait(false); } - void WrappedFunction() + void WrappedCallback() { try { @@ -99,16 +133,33 @@ void WrappedFunction() } /// - /// Executes the specified asynchronous function on the thread that owns the control's handle. + /// Executes the specified asynchronous callback on the thread that owns the control's handle asynchronously. /// /// - /// The asynchronous function to execute, - /// which takes an input of type T and returns a . + /// The asynchronous function to execute, which takes a + /// and returns a . /// /// The cancellation token. - /// A task representing the operation and containing the function's result of type T. + /// + /// A task representing the operation. + /// /// Thrown if the control's handle is not yet created. - [Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")] + /// + /// + /// Note: The callback will be marshalled to the thread that owns the control's handle, + /// and then awaited. Exceptions will be propagated back to the caller. Also note that the returned task + /// is not the task associated with the callback, but a task representing the operation of marshalling the + /// callback to the UI thread. If you need to pass a callback returning a rather than a + /// , use the ValueTask's constructor to create a new ValueTask which wraps the original + /// Task. The will be both taken into account when marshalling the callback to the + /// thread that owns the control's handle, and when executing the callback. + /// + /// + /// If you want to asynchronously execute a synchronous callback, use the overload + /// or the overload + /// . + /// + /// #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public async Task InvokeAsync(Func callback, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters @@ -124,11 +175,11 @@ public async Task InvokeAsync(Func callback, Cance using (cancellationToken.Register(() => tcs.SetCanceled(), useSynchronizationContext: false)) { - BeginInvoke(async () => await WrappedFunction().ConfigureAwait(false)); + BeginInvoke(async () => await WrappedCallbackAsync().ConfigureAwait(false)); await tcs.Task.ConfigureAwait(false); } - async Task WrappedFunction() + async Task WrappedCallbackAsync() { try { @@ -149,17 +200,32 @@ async Task WrappedFunction() } /// - /// Executes the specified asynchronous function on the thread that owns the control's handle. + /// Executes the specified asynchronous callback on the thread that owns the control's handle. /// - /// The type of the input argument to be converted into the args array. + /// The return type of the asynchronous callback. /// - /// The asynchronous function to execute, - /// which takes an input of type T and returns a . + /// The asynchronous function to execute, which takes a + /// and returns a . /// /// The cancellation token. /// A task representing the operation and containing the function's result of type T. /// Thrown if the control's handle is not yet created. - [Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")] + /// + /// + /// Note: The callback will be marshalled to the thread that owns the control's handle, + /// and then be awaited. Exceptions will be propagated back to the caller. Also note that the returned task + /// is not the task associated with the callback, but a task representing the operation of marshalling the + /// callback to the UI thread. If you need to pass a callback returning a rather than a + /// , use the ValueTask's constructor to create a new ValueTask which wraps the original + /// Task. The will be both taken into account when marshalling the callback to the + /// thread that owns the control's handle, and when executing the callback. + /// + /// + /// If you want to asynchronously execute a synchronous callback, use the overload + /// or the overload + /// . + /// + /// #pragma warning disable RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads public async Task InvokeAsync(Func> callback, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads