Open sandboxFocusImprove this doc

LA0219: Required Async Call Identifier on a Method That Is Not Async

Severity Error
Reported by PostSharp
Message The parameter "<Parameter>" of the advice "<Advice>" is bound by [AsyncCallId] with IsRequired set to true, but "<Target>" is not transformed as an async method.

Cause

A parameter of a method boundary advice annotated with AsyncCallIdAttribute receives the AsyncCallId that identifies the current execution of an async method. The identifier is stored in the state machine of the method, so only a method that becomes an async state machine has one. On another method, the parameter normally receives the default value of AsyncCallId. When IsRequired is true, PostSharp reports LA0219 instead.

PostSharp accepts an async method, and a method that returns an awaitable type, such as Task, for which PostSharp can generate a state machine. It checks again when it generates the code, and also reports LA0219 when the transformation of the method creates no async state machine. The message is reported on the target method, once for each advice that requires the identifier.

In the following example, the [TraceAsync] aspect, whose two advices require the call identifier, is applied to the synchronous Validate method.

using System;
using System.Threading.Tasks;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;

namespace Errors.LA0219_Error;

[PSerializable]
public class TraceAsyncAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    public static void OnEntry( [DeclarationName] string methodName, [AsyncCallId( IsRequired = true )] AsyncCallId callId,
                                [State( StateScope.MethodInvocation )] out AsyncCallId entryCallId )
    {
        entryCallId = callId;
        Console.WriteLine( $"Entering {methodName}." );
    }

    [OnMethodExitAdvice]
    public static void OnExit( [DeclarationName] string methodName, [AsyncCallId( IsRequired = true )] AsyncCallId callId,
                               [State( StateScope.MethodInvocation )] AsyncCallId entryCallId )
    {
        Console.WriteLine( $"Leaving {methodName}, same call as the entry: {callId == entryCallId && !callId.IsNull}." );
    }
}

public class OrderService
{
    [TraceAsync]
    public async Task<int> CountOrdersAsync()
    {
        await Task.Delay( 10 );

        return this.Validate( 3 );
    }

    // The aspect requires a call identifier, which only async methods have.
    [TraceAsync]
    public int Validate( int count ) => Math.Max( count, 0 );
}

public static class Program
{
    public static async Task Main()
    {
        int count = await new OrderService().CountOrdersAsync();
        Console.WriteLine( $"{count} orders." );
    }
}
error LA0219: LA0219_Error.cs(40,16): The parameter "callId" of the advice "Errors.LA0219_Error.TraceAsyncAttribute.OnEntry(System.String, PostSharp.Aspects.AsyncCallId, PostSharp.Aspects.AsyncCallId@)" is bound by [AsyncCallId] with IsRequired set to true, but "Errors.LA0219_Error.OrderService.Validate(System.Int32)" is not transformed as an async method.
error LA0219: LA0219_Error.cs(40,16): The parameter "callId" of the advice "Errors.LA0219_Error.TraceAsyncAttribute.OnExit(System.String, PostSharp.Aspects.AsyncCallId, PostSharp.Aspects.AsyncCallId)" is bound by [AsyncCallId] with IsRequired set to true, but "Errors.LA0219_Error.OrderService.Validate(System.Int32)" is not transformed as an async method.

Resolution

  • Apply the aspect only to async methods.
  • When the advice can work without an identifier, set IsRequired to false, which is the default, and test IsNull in the advice.

In the following example, the aspect is removed from Validate.

using System;
using System.Threading.Tasks;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;

namespace Errors.LA0219_Fixed;

[PSerializable]
public class TraceAsyncAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    public static void OnEntry( [DeclarationName] string methodName, [AsyncCallId( IsRequired = true )] AsyncCallId callId,
                                [State( StateScope.MethodInvocation )] out AsyncCallId entryCallId )
    {
        entryCallId = callId;
        Console.WriteLine( $"Entering {methodName}." );
    }

    [OnMethodExitAdvice]
    public static void OnExit( [DeclarationName] string methodName, [AsyncCallId( IsRequired = true )] AsyncCallId callId,
                               [State( StateScope.MethodInvocation )] AsyncCallId entryCallId )
    {
        Console.WriteLine( $"Leaving {methodName}, same call as the entry: {callId == entryCallId && !callId.IsNull}." );
    }
}

public class OrderService
{
    [TraceAsync]
    public async Task<int> CountOrdersAsync()
    {
        await Task.Delay( 10 );

        return this.Validate( 3 );
    }

    // The aspect is applied to async methods only.
    public int Validate( int count ) => Math.Max( count, 0 );
}

public static class Program
{
    public static async Task Main()
    {
        int count = await new OrderService().CountOrdersAsync();
        Console.WriteLine( $"{count} orders." );
    }
}
Entering CountOrdersAsync.
Leaving CountOrdersAsync, same call as the entry: True.
3 orders.

See Also

Reference

AsyncCallIdAttribute
AsyncCallId

Other Resources

Advice Parameters
Semantic Advising of Iterator and Async Methods