Should I worry about "This async method lacks 'await' operators and will run synchronously" warning
Solution 1
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; //
} // }
Solution 2
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.
Solution 3
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.
Solution 4
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.
Solution 5
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.
dannykay1710
Updated on April 01, 2022Comments
-
dannykay1710 about 2 years
I have a interface which exposes some async methods. More specifically it has methods defined which return either
Task
orTask<T>
. I am using the async/await keywords.I am in the process of implementing this interface. However, in some of these methods this implementation doesn't have anything to await. For that reason I am getting the compiler warning "CS1998: This async method lacks 'await' operators and will run synchronously..."
I understand why I am getting these warnings but am wondering whether I should do anything about them in this context. It feels wrong to ignore compiler warnings.
I know I can fix it by awaiting on
Task.Run
but that feels wrong for a method that is only doing a few inexpensive operations. It also sounds like it will add unneeded overhead to the execution but then I am also not sure if that is already there because theasync
keyword is present.Should I just ignore the warnings or is there a way of working around this that I am not seeing?
-
Servy about 9 yearsIt's going to depend on the specifics. Are you really sure you want these operations to be performed synchronously? If you do want them to be performed synchronously, why is the method marked as
async
? -
Michael Liu about 9 yearsJust remove the
async
keyword. You can still return aTask
usingTask.FromResult
. -
dannykay1710 about 9 yearsIn one example it really does just assign some values to properties so I am definitely comfortable with it executing synchronously. It's more specifically about how to handle the compiler warning which seems like could just be the obvious one of removing async keyword
-
Servy about 9 years@BenVoigt Google is full of information about it, in the event that the OP doesn't already know.
-
Admin about 9 years@BenVoigt Didn't Michael Liu already provide that hint? Use
Task.FromResult
. -
Ben Voigt about 9 years@hvd: That was edited into his comment later.
-
-
dannykay1710 about 9 yearsThanks I think the bit I was missing was the concept of creating a Task that was completed, rather than returning an actual task which like you say would be the same as having the async keyword. Seems obvious now but I just wasn't seeing it!
-
Rupert Rawnsley over 7 yearsTask could do with a static member along the lines of Task.Empty for this purpose. The intention would be a bit clearer and it pains me to think of all these dutiful Tasks that return a zero that is never needed.
-
Sushi271 over 7 years
await Task.FromResult(0)
? How aboutawait Task.Yield()
? -
Michael Liu over 7 years@Sushi271: No, in a non-
async
method, you returnTask.FromResult(0)
instead of awaiting it. -
ipavlu about 7 yearsActually NO, async is not just an implementation detail, there are many details around one has to be aware of :). One has to be aware, what part runs synchronously, what part asynchronously, what is the current synchronization context and just for the record, Tasks are always little faster, as there is not state machine behind curtains:).
-
MickyD almost 5 yearsAdditonally your final conclusion over the use of
async/await
is vastly oversimplified as you are basing it on your unrealistic example of a single operation that is CPU-bound.Task
s when used properly allows for improved application performance and responsiveness due to concurrent tasks (i.e. parallel) and better management and usage of threads -
Oleg Bondarenko almost 5 yearsThat is just test simplified example as I said in this post. Also I mentioned about requests to api and event hendlers where posible using both version of methods (async and regular). Also PO said about using async methods without await inside. My post was about it but not about properly using
Tasks
. It is sad story that you are not reading whole text of post and doing quickly conclusions. -
MickyD almost 5 yearsThere is a difference between a method that returns
int
(as in your case) and one that returnsTask
such as discussed by the OP. Read his post and the accepted answer again instead of taking things personally. Your answer isn't helpful in this case. You don't even bother to show the difference between a method that hasawait
inside or not. Now had you done that that would have been very good well worth an upvote -
Oleg Bondarenko almost 5 yearsI guess you are really don't understand difference between async method and regular ones that are called with api or event handlers. It was specially mentioned in my post. Sorry for you are missing that again.
-
Victor Yarema over 4 yearsThis answer is just wrong. Here's why: there can be at least one
await
inside the method in one place (there will be no CS1998) but it doesn't mean that there will be no other call of asnyc method that will lack the synchronization (usingawait
or any other). Now if someone would like to know how to make sure you don't miss the synchronization accidentally then just make sure you don't ignore another warning - CS4014. I would even recommend to threat that one as error. -
wensveen about 4 yearsI wish the await keyword (or perhaps another keyword specifically for this purpose) would just wrap any non-Task type in a completed Task.
-
Ben Voigt almost 4 years@wensveen: The
async
keyword does that. It also generates a compile warning. -
Rebecca about 3 yearsI've just hit an interesting issue regarding this. Because I had an interface that was returning a Task, but my a few of my concrete implementations had nothing to asynchronously call, and some of them needed to return null to exit early, I had been using return Task.FromResult(<ReturnClass>null);. The interesting weird side effect that I had not expected was that when the method raised an exception, the only clue was a "object not set to an instance of an object" exception, but on the line calling the async method, which made zero sense at the time and took me a while to debug. Nice answer!