Open sandboxFocusImprove this doc

LA0244: Required Current Task with an Awaiter in the Entry Advice

Severity Error
Reported by PostSharp
Message The parameter "<Parameter>" of the advice "<Advice>" is bound by [CurrentTask] with IsRequired set to true, but the entry advice "<EntryAdvice>" has an [Awaiter] parameter: when it yields on an awaiter that is already completed, the resume advice runs before the method has yielded, when the task does not exist yet.

Cause

A [CurrentTaskAttribute] parameter receives the task that the caller of an async method observes. This task exists only once the method has yielded. With IsRequired set to true, the advice guarantees that the parameter is not null, which is accepted only in an OnMethodResumeAdvice, the advice that normally runs after the method has yielded.

An OnMethodEntryAdvice with an [AwaiterAttribute] parameter breaks this guarantee. It can set FlowBehavior.Yield and give an awaiter that is already completed. The method then does not suspend: the resume advice runs at once, in the same step of the state machine, before the method has yielded and before the task exists. PostSharp reports LA0244 on the <Parameter> parameter of the resume advice <Advice> when an entry advice <EntryAdvice> of the same advice group has an [AwaiterAttribute] parameter, whatever awaiter it gives at run time.

In the following example, the entry advice of [RunInBackground] yields on an awaiter, and the resume advice requires the task.

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

namespace Errors.LA0244_Error;

[PSerializable]
public class RunInBackgroundAttribute : MethodLevelAspect
{
    // The entry advice makes the method await the awaiter before it continues.
    [OnMethodEntryAdvice]
    public static void OnEntry( [FlowBehavior] out FlowBehavior flowBehavior, [Awaiter] out YieldAwaitable.YieldAwaiter awaiter )
    {
        awaiter = Task.Yield().GetAwaiter();
        flowBehavior = FlowBehavior.Yield;
    }

    [OnMethodResumeAdvice]
    public static void OnResume(
        // The resume advice can run before the method has yielded, when the task does not exist yet.
        [CurrentTask( IsRequired = true )] Task task )
    {
        Console.WriteLine( $"Resuming task {task.Id}." );
    }
}

public class ReportService
{
    [RunInBackground]
    public async Task GenerateAsync()
    {
        await Task.Delay( 10 );
        Console.WriteLine( "Report generated." );
    }
}

public static class Program
{
    public static void Main()
    {
        new ReportService().GenerateAsync().Wait();
    }
}
error LA0244: LA0244_Error.cs(24,49): The parameter "task" of the advice "Errors.LA0244_Error.RunInBackgroundAttribute.OnResume(System.Threading.Tasks.Task)" is bound by [CurrentTask] with IsRequired set to true, but the entry advice "Errors.LA0244_Error.RunInBackgroundAttribute.OnEntry(PostSharp.Aspects.FlowBehavior@, System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter@)" has an [Awaiter] parameter: when it yields on an awaiter that is already completed, the resume advice runs before the method has yielded, when the task does not exist yet.

Resolution

Remove IsRequired from the [CurrentTaskAttribute] parameter, and handle a null task in the resume advice. When the advice cannot do without the task, read it in another advice that runs later, such as the success or exit advice of an execution that has yielded, or remove the [AwaiterAttribute] parameter from the entry advice.

In the following example, the resume advice accepts a null task.

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

namespace Errors.LA0244_Fixed;

[PSerializable]
public class RunInBackgroundAttribute : MethodLevelAspect
{
    // The entry advice makes the method await the awaiter before it continues.
    [OnMethodEntryAdvice]
    public static void OnEntry( [FlowBehavior] out FlowBehavior flowBehavior, [Awaiter] out YieldAwaitable.YieldAwaiter awaiter )
    {
        awaiter = Task.Yield().GetAwaiter();
        flowBehavior = FlowBehavior.Yield;
    }

    [OnMethodResumeAdvice]
    public static void OnResume(
        // The task is not required, so the advice handles the case where it does not exist yet.
        [CurrentTask] Task? task )
    {
        Console.WriteLine( task != null ? "Resuming the task." : "Resuming before the task exists." );
    }
}

public class ReportService
{
    [RunInBackground]
    public async Task GenerateAsync()
    {
        await Task.Delay( 10 );
        Console.WriteLine( "Report generated." );
    }
}

public static class Program
{
    public static void Main()
    {
        new ReportService().GenerateAsync().Wait();
    }
}
Resuming the task.
Resuming the task.
Report generated.

See Also

Reference

CurrentTaskAttribute
AwaiterAttribute
OnMethodResumeAdvice

Other Resources

Advice Parameters