CsWinRT icon indicating copy to clipboard operation
CsWinRT copied to clipboard

Proposal: AsyncMethodBuilder for IAsyncAction and IAsyncOperation

Open wherewhere opened this issue 3 months ago • 4 comments

Summary

In C# 10, we can create a custom async builder for return types. So that we can create builders for IAsyncAction and IAsyncOperation to use them without writing AsyncInfo.Run.

For now, we can create builders base on global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder and return the task with the conversation by global::System.WindowsRuntimeSystemExtensions.AsAsyncAction and global::System.WindowsRuntimeSystemExtensions.AsAsyncOperation.
(Maybe it has a better implementation but I don't want to think about it...

/// <summary>
/// Provides a builder for asynchronous methods that return <see cref="global::Windows.Foundation.IAsyncAction"/>.
/// This type is intended for compiler use only.
/// </summary>
public struct AsyncActionMethodBuilder
{
    /// <summary>
    /// The underlying builder.
    /// </summary>
    private global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder _builder;

    /// <summary>
    /// Initializes a new <see cref="AsyncActionMethodBuilder"/> struct with the specified builder.
    /// </summary>
    /// <param name="builder">The underlying builder.</param>
    public AsyncActionMethodBuilder(in global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder builder)
    {
        _builder = builder;
    }

    /// <summary>
    /// Initializes a new <see cref="AsyncActionMethodBuilder"/>.
    /// </summary>
    /// <returns>The initialized <see cref="AsyncActionMethodBuilder"/>.</returns>
    public static AsyncActionMethodBuilder Create()
    {
        return new AsyncActionMethodBuilder(global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create());
    }

    /// <summary>
    /// Initiates the builder's execution with the associated state machine.
    /// </summary>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="stateMachine">The state machine instance, passed by reference.</param>
    [global::System.Diagnostics.DebuggerStepThrough]
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.Start(ref stateMachine);
    }

    /// <summary>
    /// Associates the builder with the state machine it represents.
    /// </summary>
    /// <param name="stateMachine">The heap-allocated state machine object.</param>
    /// <exception cref="global::System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (<see langword="Nothing" /> in Visual Basic).</exception>
    /// <exception cref="global::System.InvalidOperationException">The builder is incorrectly initialized.</exception>
    public void SetStateMachine(global::System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)
    {
        _builder.SetStateMachine(stateMachine);
    }

    /// <summary>
    /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
    /// </summary>
    /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="awaiter">The awaiter.</param>
    /// <param name="stateMachine">The state machine.</param>
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : global::System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
    }

    /// <summary>
    /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
    /// </summary>
    /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="awaiter">The awaiter.</param>
    /// <param name="stateMachine">The state machine.</param>
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : global::System.Runtime.CompilerServices.ICriticalNotifyCompletion
        where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
    }

    /// <summary>
    /// Gets the <see cref="global::Windows.Foundation.IAsyncAction"/> for this builder.
    /// </summary>
    /// <returns>The <see cref="global::Windows.Foundation.IAsyncAction"/> representing the builder's asynchronous operation.</returns>
    /// <exception cref="global::System.InvalidOperationException">The builder is not initialized.</exception>
    public global::Windows.Foundation.IAsyncAction Task
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get
        {
            return global::System.WindowsRuntimeSystemExtensions.AsAsyncAction(_builder.Task);
        }
    }

    /// <summary>
    /// Completes the <see cref="global::Windows.Foundation.IAsyncAction"/> in the
    /// <see cref="TaskStatus">RanToCompletion</see> state.
    /// </summary>
    /// <exception cref="global::System.InvalidOperationException">The builder is not initialized.</exception>
    /// <exception cref="global::System.InvalidOperationException">The task has already completed.</exception>
    public void SetResult()
    {
        _builder.SetResult();
    }

    /// <summary>
    /// Completes the <see cref="global::Windows.Foundation.IAsyncAction"/> in the
    /// <see cref="TaskStatus">Faulted</see> state with the specified exception.
    /// </summary>
    /// <param name="exception">The <see cref="global::System.Exception"/> to use to fault the task.</param>
    /// <exception cref="global::System.ArgumentNullException">The <paramref name="exception"/> argument is null (<see langword="Nothing" /> in Visual Basic).</exception>
    /// <exception cref="global::System.InvalidOperationException">The builder is not initialized.</exception>
    /// <exception cref="global::System.InvalidOperationException">The task has already completed.</exception>
    public void SetException(global::System.Exception exception)
    {
        _builder.SetException(exception);
    }
}
/// <summary>
/// Provides a builder for asynchronous methods that return <see cref="global::Windows.Foundation.IAsyncOperation{TResult}"/>.
/// This type is intended for compiler use only.
/// </summary>
public struct AsyncOperationMethodBuilder<TResult>
{
    /// <summary>
    /// The underlying builder.
    /// </summary>
    private global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult> _builder;

    /// <summary>
    /// Initializes a new <see cref="AsyncOperationMethodBuilder{TResult}"/> struct.
    /// </summary>
    /// <param name="builder">The underlying builder.</param>
    public AsyncOperationMethodBuilder(in global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult> builder)
    {
        _builder = builder;
    }

    /// <summary>
    /// Initializes a new <see cref="AsyncOperationMethodBuilder{TResult}"/>.
    /// </summary>
    /// <returns>The initialized <see cref="AsyncOperationMethodBuilder{TResult}"/>.</returns>
    public static AsyncOperationMethodBuilder<TResult> Create()
    {
        return new AsyncOperationMethodBuilder<TResult>(global::System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>.Create());
    }

    /// <summary>
    /// Initiates the builder's execution with the associated state machine.
    /// </summary>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="stateMachine">The state machine instance, passed by reference.</param>
    [global::System.Diagnostics.DebuggerStepThrough]
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.Start(ref stateMachine);
    }

    /// <summary>
    /// Associates the builder with the state machine it represents.
    /// </summary>
    /// <param name="stateMachine">The heap-allocated state machine object.</param>
    /// <exception cref="global::System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (<see langword="Nothing" /> in Visual Basic).</exception>
    /// <exception cref="global::System.InvalidOperationException">The builder is incorrectly initialized.</exception>
    public void SetStateMachine(global::System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)
    {
        _builder.SetStateMachine(stateMachine);
    }

    /// <summary>
    /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
    /// </summary>
    /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="awaiter">The awaiter.</param>
    /// <param name="stateMachine">The state machine.</param>
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : global::System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
    }

    /// <summary>
    /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
    /// </summary>
    /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
    /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
    /// <param name="awaiter">The awaiter.</param>
    /// <param name="stateMachine">The state machine.</param>
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : global::System.Runtime.CompilerServices.ICriticalNotifyCompletion
        where TStateMachine : global::System.Runtime.CompilerServices.IAsyncStateMachine
    {
        _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
    }

    /// <summary>
    /// Gets the <see cref="global::Windows.Foundation.IAsyncOperation{TResult}"/> for this builder.
    /// </summary>
    /// <returns>The <see cref="global::Windows.Foundation.IAsyncOperation{TResult}"/> representing the builder's asynchronous operation.</returns>
    public global::Windows.Foundation.IAsyncOperation<TResult> Task
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get
        {
            return global::System.WindowsRuntimeSystemExtensions.AsAsyncOperation<TResult>(_builder.Task);
        }
    }

    /// <summary>
    /// Completes the <see cref="global::Windows.Foundation.IAsyncAction"/> in the
    /// <see cref="TaskStatus">RanToCompletion</see> state.
    /// </summary>
    /// <param name="result">The result to use to complete the task.</param>
    /// <exception cref="global::System.InvalidOperationException">The task has already completed.</exception>
    public void SetResult(TResult result)
    {
        _builder.SetResult(result);
    }

    /// <summary>
    /// Completes the <see cref="global::Windows.Foundation.IAsyncAction"/> in the
    /// <see cref="TaskStatus">Faulted</see> state with the specified exception.
    /// </summary>
    /// <param name="exception">The <see cref="global::System.Exception"/> to use to fault the task.</param>
    /// <exception cref="global::System.ArgumentNullException">The <paramref name="exception"/> argument is null (<see langword="Nothing" /> in Visual Basic).</exception>
    /// <exception cref="global::System.InvalidOperationException">The task has already completed.</exception>
    public void SetException(global::System.Exception exception)
    {
        _builder.SetException(exception);
    }
}

Then we can write methods with IAsyncAction and IAsyncOperation return type now.

[System.Runtime.CompilerServices.AsyncMethodBuilder(typeof(AsyncActionMethodBuilder))]
public static async IAsyncAction TestAsync()
{
    await Task.Yield();
}

[System.Runtime.CompilerServices.AsyncMethodBuilder(typeof(AsyncOperationMethodBuilder<>))]
public static async IAsyncOperation<int> TestAsync(int a)
{
    await Task.Yield();
    return a;
}

Rationale

  • Write IAsyncAction and IAsyncOperation method easily.

Important Notes

No response

Open Questions

No response

wherewhere avatar Aug 28 '25 07:08 wherewhere

That's not a new thing, but comes with quite a lot of complexity.

dongle-the-gadget avatar Aug 29 '25 12:08 dongle-the-gadget

😕

wherewhere avatar Aug 29 '25 14:08 wherewhere

I really like this idea! As far as I can tell, the AsyncMethodBuilder structs should not be readonly though.

The SetResult method will try to mutate the inner AsyncTaskMethodBuilder. But because the outer AsyncOperationMethodBuilder struct is readonly the result will be set on a copy of the original builder.

ShortDevelopment avatar Sep 04 '25 13:09 ShortDevelopment

I see, removed.

wherewhere avatar Sep 04 '25 15:09 wherewhere