Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I worry about "This async method lacks 'await' operators and will run synchronously" warning

People also ask

What happens if you do not await an async method?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Does async await make it synchronous?

Async/await helps you write synchronous-looking JavaScript code that works asynchronously. Await is in an async function to ensure that all promises that are returned in the function are synchronized. With async/await, there's no use of callbacks.

Should you always await async methods?

If a method is declared async, make sure there is an await! If your code does not have an await in its body, the compiler will generate a warning but the state machine will be created nevertheless, adding unnecessary overhead for an operation that will actually never yield.

How do you call async method without await?

However, just to address "Call an async method in C# without await", you can execute the async method inside a Task. Run . This approach will wait until MyAsyncMethod finish. await asynchronously unwraps the Result of your task, whereas just using Result would block until the task had completed.


The async keyword is merely an implementation detail of a method; it isn't part of the method signature. If one particular method implementation or override has nothing to await, then just omit the async keyword and return a completed task using Task.FromResult<TResult>:

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

If your method returns Task instead of Task<TResult>, then you can return a completed task of any type and value. Task.FromResult(0) seems to be a popular choice:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

Or, as of .NET Framework 4.6, you can return Task.CompletedTask:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }

It's perfectly reasonable that some "asynchronous" operations complete synchronously, yet still conform to the asynchronous call model for the sake of polymorphism.

A real-world example of this is with the OS I/O APIs. Asynchronous and overlapped calls on some devices always complete inline (writing to a pipe implemented using shared memory, for example). But they implement the same interface as multi-part operations which do continue in the background.


Michael Liu answered well your question about how you can avoid the warning: by returning Task.FromResult.

I'm going to answer the "Should I worry about the warning" part of your question.

The answer is Yes!

The reason for this is that the warning frequently results when you call a method that returns Task inside of an async method without the await operator. I just fixed a concurrency bug that happened because I invoked an operation in Entity Framework without awaiting the previous operation.

If you can meticulously write your code to avoid compiler warnings, then when there is a warning, it will stand out like a sore thumb. I could have avoided several hours of debugging.


It might be too late but it might be useful investigation:

There is about inner structure of compiled code (IL):

 public static async Task<int> GetTestData()
    {
        return 12;
    }

it becomes to in IL:

.method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> 
        GetTestData() cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E   // ..(UsageLibrary.
                                                                                                                                     53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65   // StartType+<GetTe
                                                                                                                                     73 74 44 61 74 61 3E 64 5F 5F 31 00 00 )          // stData>d__1..
  .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  2
  .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0,
           [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1)
  IL_0000:  newobj     instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
  IL_000c:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.m1
  IL_0013:  stfld      int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state'
  IL_0018:  ldloc.0
  IL_0019:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_001e:  stloc.1
  IL_001f:  ldloca.s   V_1
  IL_0021:  ldloca.s   V_0
  IL_0023:  call       instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&)
  IL_0028:  ldloc.0
  IL_0029:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_002e:  call       instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
  IL_0033:  ret
} // end of method StartType::GetTestData

And without async and task method:

 public static int GetTestData()
        {
            return 12;
        }

becomes :

.method private hidebysig static int32  GetTestData() cil managed
{
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   12
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method StartType::GetTestData

As you could see the big difference between these methods. If you don't use await inside async method and do not care about using of async method (for example API call or event handler) the good idea will convert it to normal sync method (it saves your application performance).

Updated:

There is also additional information from microsoft docs:

async methods need to have an await keyword in their body or they will never yield! This is important to keep in mind. If await is not used in the body of an async method, the C# compiler will generate a warning, but the code will compile and run as if it were a normal method. Note that this would also be incredibly inefficient, as the state machine generated by the C# compiler for the async method would not be accomplishing anything.


Only if you are actually calling the method involved, and only if performance is a concern.

This can be demonstrated by writing a program containing the following 4 methods, then decompiling them to IL (note that IL presented may change between runtime versions; the below is from .NET Core 3.1):

int FullySync() => 42;

Task<int> TaskSync() => Task.FromResult(42);

// CS1998
async Task<int> NotActuallyAsync() => 42;

async Task<int> FullyAsync() => await Task.Run(() => 42);

The first two result in very short method bodies containing exactly what you would expect:

.method private hidebysig 
    instance int32 FullySync () cil managed 
{
    // Method begins at RVA 0x205e
    // Code size 3 (0x3)
    .maxstack 8

    // return 42;
    IL_0000: ldc.i4.s 42
    IL_0002: ret
} // end of method Program::FullySync

.method private hidebysig 
    instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> TaskSync () cil managed 
{
    // Method begins at RVA 0x2062
    // Code size 8 (0x8)
    .maxstack 8

    // return Task.FromResult(42);
    IL_0000: ldc.i4.s 42
    IL_0002: call class [System.Runtime]System.Threading.Tasks.Task`1<!!0> [System.Runtime]System.Threading.Tasks.Task::FromResult<int32>(!!0)
    IL_0007: ret
} // end of method Program::TaskSync

But the presence of the async keyword on the last two causes the compiler to generate asynchronous state machines for those methods:

.method private hidebysig 
    instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> NotActuallyAsync () cil managed 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
        01 00 29 43 53 31 39 39 38 54 65 73 74 2e 50 72
        6f 67 72 61 6d 2b 3c 4e 6f 74 41 63 74 75 61 6c
        6c 79 41 73 79 6e 63 3e 64 5f 5f 33 00 00
    )
    .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x206c
    // Code size 56 (0x38)
    .maxstack 2
    .locals init (
        [0] class CS1998Test.Program/'<NotActuallyAsync>d__3'
    )

    IL_0000: newobj instance void CS1998Test.Program/'<NotActuallyAsync>d__3'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
    IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
    IL_0011: ldloc.0
    IL_0012: ldarg.0
    IL_0013: stfld class CS1998Test.Program CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>4__this'
    IL_0018: ldloc.0
    IL_0019: ldc.i4.m1
    IL_001a: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
    IL_001f: ldloc.0
    IL_0020: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
    IL_0025: ldloca.s 0
    IL_0027: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class CS1998Test.Program/'<NotActuallyAsync>d__3'>(!!0&)
    IL_002c: ldloc.0
    IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
    IL_0032: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
    IL_0037: ret
} // end of method Program::NotActuallyAsync

.class nested private auto ansi sealed beforefieldinit '<NotActuallyAsync>d__3'
    extends [System.Runtime]System.Object
    implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Fields
    .field public int32 '<>1__state'
    .field public valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder'
    .field public class CS1998Test.Program '<>4__this'

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20fd
        // Code size 8 (0x8)
        .maxstack 8

        // {
        IL_0000: ldarg.0
        // (no C# code)
        IL_0001: call instance void [System.Runtime]System.Object::.ctor()
        // }
        IL_0006: nop
        IL_0007: ret
    } // end of method '<NotActuallyAsync>d__3'::.ctor

    .method private final hidebysig newslot virtual 
        instance void MoveNext () cil managed 
    {
        .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
        // Method begins at RVA 0x2108
        // Code size 58 (0x3a)
        .maxstack 2
        .locals init (
            [0] int32,
            [1] int32,
            [2] class [System.Runtime]System.Exception
        )

        // int num = <>1__state;
        IL_0000: ldarg.0
        IL_0001: ldfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
        IL_0006: stloc.0
        .try
        {
            // result = 42;
            IL_0007: ldc.i4.s 42
            IL_0009: stloc.1
            // }
            IL_000a: leave.s IL_0024
        } // end .try
        catch [System.Runtime]System.Exception
        {
            // catch (Exception exception)
            IL_000c: stloc.2
            // <>1__state = -2;
            IL_000d: ldarg.0
            IL_000e: ldc.i4.s -2
            IL_0010: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
            // <>t__builder.SetException(exception);
            IL_0015: ldarg.0
            IL_0016: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
            IL_001b: ldloc.2
            IL_001c: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [System.Runtime]System.Exception)
            // return;
            IL_0021: nop
            IL_0022: leave.s IL_0039
        } // end handler

        // <>1__state = -2;
        IL_0024: ldarg.0
        IL_0025: ldc.i4.s -2
        IL_0027: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
        // <>t__builder.SetResult(result);
        IL_002c: ldarg.0
        IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
        IL_0032: ldloc.1
        IL_0033: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
        // }
        IL_0038: nop

        IL_0039: ret
    } // end of method '<NotActuallyAsync>d__3'::MoveNext

    .method private final hidebysig newslot virtual 
        instance void SetStateMachine (
            class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
        ) cil managed 
    {
        .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
            01 00 00 00
        )
        .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine)
        // Method begins at RVA 0x2160
        // Code size 1 (0x1)
        .maxstack 8

        // }
        IL_0000: ret
    } // end of method '<NotActuallyAsync>d__3'::SetStateMachine

} // end of class <NotActuallyAsync>d__3

.method private hidebysig 
    instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> FullyAsync () cil managed 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
        01 00 23 43 53 31 39 39 38 54 65 73 74 2e 50 72
        6f 67 72 61 6d 2b 3c 46 75 6c 6c 79 41 73 79 6e
        63 3e 64 5f 5f 34 00 00
    )
    .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x20b0
    // Code size 56 (0x38)
    .maxstack 2
    .locals init (
        [0] class CS1998Test.Program/'<FullyAsync>d__4'
    )

    IL_0000: newobj instance void CS1998Test.Program/'<FullyAsync>d__4'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
    IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
    IL_0011: ldloc.0
    IL_0012: ldarg.0
    IL_0013: stfld class CS1998Test.Program CS1998Test.Program/'<FullyAsync>d__4'::'<>4__this'
    IL_0018: ldloc.0
    IL_0019: ldc.i4.m1
    IL_001a: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
    IL_001f: ldloc.0
    IL_0020: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
    IL_0025: ldloca.s 0
    IL_0027: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class CS1998Test.Program/'<FullyAsync>d__4'>(!!0&)
    IL_002c: ldloc.0
    IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
    IL_0032: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
    IL_0037: ret
} // end of method Program::FullyAsync

.class nested private auto ansi sealed beforefieldinit '<FullyAsync>d__4'
    extends [System.Runtime]System.Object
    implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Fields
    .field public int32 '<>1__state'
    .field public valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder'
    .field public class CS1998Test.Program '<>4__this'
    .field private int32 '<>s__1'
    .field private valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> '<>u__1'

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x217b
        // Code size 8 (0x8)
        .maxstack 8

        // {
        IL_0000: ldarg.0
        // (no C# code)
        IL_0001: call instance void [System.Runtime]System.Object::.ctor()
        // }
        IL_0006: nop
        IL_0007: ret
    } // end of method '<FullyAsync>d__4'::.ctor

    .method private final hidebysig newslot virtual 
        instance void MoveNext () cil managed 
    {
        .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
        // Method begins at RVA 0x2184
        // Code size 199 (0xc7)
        .maxstack 3
        .locals init (
            [0] int32,
            [1] int32,
            [2] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>,
            [3] class CS1998Test.Program/'<FullyAsync>d__4',
            [4] class [System.Runtime]System.Exception
        )

        // int num = <>1__state;
        IL_0000: ldarg.0
        IL_0001: ldfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
        IL_0006: stloc.0
        .try
        {
            // if (num != 0)
            IL_0007: ldloc.0
            IL_0008: brfalse.s IL_000c

            // (no C# code)
            IL_000a: br.s IL_000e

            // awaiter = Task.Run(() => 42).GetAwaiter();
            IL_000c: br.s IL_0065

            IL_000e: ldsfld class [System.Runtime]System.Func`1<int32> CS1998Test.Program/'<>c'::'<>9__4_0'
            IL_0013: dup
            IL_0014: brtrue.s IL_002d

            // (no C# code)
            IL_0016: pop
            // if (!awaiter.IsCompleted)
            IL_0017: ldsfld class CS1998Test.Program/'<>c' CS1998Test.Program/'<>c'::'<>9'
            IL_001c: ldftn instance int32 CS1998Test.Program/'<>c'::'<FullyAsync>b__4_0'()
            IL_0022: newobj instance void class [System.Runtime]System.Func`1<int32>::.ctor(object, native int)
            IL_0027: dup
            IL_0028: stsfld class [System.Runtime]System.Func`1<int32> CS1998Test.Program/'<>c'::'<>9__4_0'

            IL_002d: call class [System.Runtime]System.Threading.Tasks.Task`1<!!0> [System.Runtime]System.Threading.Tasks.Task::Run<int32>(class [System.Runtime]System.Func`1<!!0>)
            IL_0032: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [System.Runtime]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
            IL_0037: stloc.2
            IL_0038: ldloca.s 2
            IL_003a: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::get_IsCompleted()
            IL_003f: brtrue.s IL_0081

            // num = (<>1__state = 0);
            IL_0041: ldarg.0
            IL_0042: ldc.i4.0
            IL_0043: dup
            IL_0044: stloc.0
            IL_0045: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
            // <>u__1 = awaiter;
            IL_004a: ldarg.0
            IL_004b: ldloc.2
            IL_004c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
            // <FullyAsync>d__4 stateMachine = this;
            IL_0051: ldarg.0
            IL_0052: stloc.3
            // <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
            IL_0053: ldarg.0
            IL_0054: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
            IL_0059: ldloca.s 2
            IL_005b: ldloca.s 3
            IL_005d: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::AwaitUnsafeOnCompleted<valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>, class CS1998Test.Program/'<FullyAsync>d__4'>(!!0&, !!1&)
            // return;
            IL_0062: nop
            IL_0063: leave.s IL_00c6

            // awaiter = <>u__1;
            IL_0065: ldarg.0
            IL_0066: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
            IL_006b: stloc.2
            // <>u__1 = default(TaskAwaiter<int>);
            IL_006c: ldarg.0
            IL_006d: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
            IL_0072: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>
            // num = (<>1__state = -1);
            IL_0078: ldarg.0
            IL_0079: ldc.i4.m1
            IL_007a: dup
            IL_007b: stloc.0
            IL_007c: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'

            // <>s__1 = awaiter.GetResult();
            IL_0081: ldarg.0
            IL_0082: ldloca.s 2
            IL_0084: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::GetResult()
            IL_0089: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>s__1'
            // result = <>s__1;
            IL_008e: ldarg.0
            IL_008f: ldfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>s__1'
            IL_0094: stloc.1
            // }
            IL_0095: leave.s IL_00b1
        } // end .try
        catch [System.Runtime]System.Exception
        {
            // catch (Exception exception)
            IL_0097: stloc.s 4
            // <>1__state = -2;
            IL_0099: ldarg.0
            IL_009a: ldc.i4.s -2
            IL_009c: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
            // <>t__builder.SetException(exception);
            IL_00a1: ldarg.0
            IL_00a2: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
            IL_00a7: ldloc.s 4
            IL_00a9: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [System.Runtime]System.Exception)
            // return;
            IL_00ae: nop
            IL_00af: leave.s IL_00c6
        } // end handler

        // <>1__state = -2;
        IL_00b1: ldarg.0
        IL_00b2: ldc.i4.s -2
        IL_00b4: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
        // <>t__builder.SetResult(result);
        IL_00b9: ldarg.0
        IL_00ba: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
        IL_00bf: ldloc.1
        IL_00c0: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
        // }
        IL_00c5: nop

        IL_00c6: ret
    } // end of method '<FullyAsync>d__4'::MoveNext

    .method private final hidebysig newslot virtual 
        instance void SetStateMachine (
            class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
        ) cil managed 
    {
        .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
            01 00 00 00
        )
        .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine)
        // Method begins at RVA 0x2268
        // Code size 1 (0x1)
        .maxstack 8

        // }
        IL_0000: ret
    } // end of method '<FullyAsync>d__4'::SetStateMachine

} // end of class <FullyAsync>d__4

In brief, executing a method marked with the async modifier entails the construction and execution of an asynchronous state machine for that method, regardless of whether that method is actually performing any asynchronous work! As I'm sure you can guess, that entails a performance penalty compared to a standard non-async method, which - depending on your use-case - may or may not be significant.

But this isn't what the CS1998 warning says at all. This warning is intended for the case where you've defined an async method because you need to await something in it, but have simply forgotten to add the await keyword before the asynchronous call.

Your case is essentially the opposite: you've defined a method as async but you know and intend that it doesn't perform any such work. But the compiler has no way of knowing that - to the compiler it looks exactly the same as the previous case, so you get the same warning.

To be honest, in that second case you yourself have caused the warning by unnecessarily adding the async keyword to the implementation. You know that the method isn't doing any asynchronous work, so why bother adding the keyword? You're just bloating it for no good reason.

The warning could certainly be improved to call out the fact that you're basically being silly, and I've opened an issue in the Roslyn repo to hopefully get that done.