Skip to content

Commit

Permalink
Finalize Invoke async including the docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
KlausLoeffelmann committed Aug 8, 2024
1 parent d80444a commit 51e9159
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/System.Windows.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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<System.Windows.Forms.TaskDialogButton!>!
static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
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<System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
System.Windows.Forms.Control.InvokeAsync<T>(System.Func<System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<T>>! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<T>!
System.Windows.Forms.Control.InvokeAsync<T>(System.Func<T>! callback, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<T>!
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <param name="action">The synchronous action to execute.</param>
/// <param name="callback">The synchronous action to execute.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the operation and containing the function's result.</returns>
[Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")]
/// <returns>A task representing the operation.</returns>
/// <remarks>
/// <para>
/// <b>Note:</b> When you pass a <see cref="CancellationToken"/> 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.
/// </para>
/// <para>
/// If you want to execute a long-running operation, consider using asynchronous callbacks instead,
/// by making sure that you use either the overload
/// <see cref="InvokeAsync(Func{CancellationToken, ValueTask}, CancellationToken)"/> or
/// <see cref="InvokeAsync{T}(Func{CancellationToken, ValueTask{T}}, CancellationToken)"/>.
/// </para>
/// </remarks>
#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;
Expand All @@ -41,7 +54,7 @@ void WrappedAction()
return;
}

action();
callback();
tcs.TrySetResult();
}
catch (Exception ex)
Expand All @@ -52,13 +65,34 @@ void WrappedAction()
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The return type of the synchronous function.</typeparam>
/// <typeparam name="T">The return type of the synchronous callback.</typeparam>
/// <param name="callback">The synchronous function to execute.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the operation and containing the function's result.</returns>
[Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")]
/// <remarks>
/// <para>
/// <b>Note:</b> When you pass a <see cref="CancellationToken"/> 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.
/// </para>
/// <para>
/// If you want to execute a long-running operation, consider using asynchronous callbacks instead, which you use
/// with the overloads of InvokeAsync described below.
/// </para>
/// <para>
/// <b>Important:</b> Also note that if you use this overload to pass a callback which returns a <see cref="Task"/>
/// 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
/// <see cref="InvokeAsync(Func{CancellationToken, ValueTask}, CancellationToken)"/> or
/// <see cref="InvokeAsync{T}(Func{CancellationToken, ValueTask{T}}, CancellationToken)"/>.
/// </para>
/// </remarks>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
public async Task<T> InvokeAsync<T>(Func<T> callback, CancellationToken cancellationToken = default)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
Expand All @@ -74,11 +108,11 @@ public async Task<T> InvokeAsync<T>(Func<T> 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
{
Expand All @@ -99,16 +133,33 @@ void WrappedFunction()
}

/// <summary>
/// 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.
/// </summary>
/// <param name="callback">
/// The asynchronous function to execute,
/// which takes an input of type T and returns a <see cref="ValueTask{T}"/>.
/// The asynchronous function to execute, which takes a <see cref="CancellationToken"/>
/// and returns a <see cref="ValueTask"/>.
/// </param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the operation and containing the function's result of type T.</returns>
/// <returns>
/// A task representing the operation.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown if the control's handle is not yet created.</exception>
[Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")]
/// <remarks>
/// <para>
/// <b>Note:</b> 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 <see cref="Task"/> rather than a
/// <see cref="ValueTask"/>, use the ValueTask's constructor to create a new ValueTask which wraps the original
/// Task. The <see cref="CancellationToken"/> will be both taken into account when marshalling the callback to the
/// thread that owns the control's handle, and when executing the callback.
/// </para>
/// <para>
/// If you want to asynchronously execute a synchronous callback, use the overload
/// <see cref="InvokeAsync{T}(Func{T}, CancellationToken)"/> or the overload
/// <see cref="InvokeAsync(Action, CancellationToken)"/>.
/// </para>
/// </remarks>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
public async Task InvokeAsync(Func<CancellationToken, ValueTask> callback, CancellationToken cancellationToken = default)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
Expand All @@ -124,11 +175,11 @@ public async Task InvokeAsync(Func<CancellationToken, ValueTask> 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
{
Expand All @@ -149,17 +200,32 @@ async Task WrappedFunction()
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the input argument to be converted into the args array.</typeparam>
/// <typeparam name="T">The return type of the asynchronous callback.</typeparam>
/// <param name="callback">
/// The asynchronous function to execute,
/// which takes an input of type T and returns a <see cref="ValueTask{T}"/>.
/// The asynchronous function to execute, which takes a <see cref="CancellationToken"/>
/// and returns a <see cref="ValueTask{T}"/>.
/// </param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the operation and containing the function's result of type T.</returns>
/// <exception cref="InvalidOperationException">Thrown if the control's handle is not yet created.</exception>
[Experimental(DiagnosticIDs.ExperimentalAsync, UrlFormat = "https://aka.ms/winforms-experimental/{0}")]
/// <remarks>
/// <para>
/// <b>Note:</b> 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 <see cref="Task"/> rather than a
/// <see cref="ValueTask"/>, use the ValueTask's constructor to create a new ValueTask which wraps the original
/// Task. The <see cref="CancellationToken"/> will be both taken into account when marshalling the callback to the
/// thread that owns the control's handle, and when executing the callback.
/// </para>
/// <para>
/// If you want to asynchronously execute a synchronous callback, use the overload
/// <see cref="InvokeAsync{T}(Func{T}, CancellationToken)"/> or the overload
/// <see cref="InvokeAsync(Action, CancellationToken)"/>.
/// </para>
/// </remarks>
#pragma warning disable RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads
public async Task<T> InvokeAsync<T>(Func<CancellationToken, ValueTask<T>> callback, CancellationToken cancellationToken = default)
#pragma warning restore RS0026 // API with optional parameter(s) should have the most parameters amongst its public overloads
Expand Down

0 comments on commit 51e9159

Please sign in to comment.