I have a interface which exposes some async methods. More specifically it has methods defined which return either Task or Task
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 https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth:
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.
Task.FromResult
Here's a little demo which shows the difference in exception handling between methods marked and not marked with async
.
public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!");
// Warning: This async method lacks 'await' operators and will run synchronously. Consider ...
public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!");
public string GetToken3Throws() => throw new Exception("Ex3!");
public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws);
public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");}
public static async Task Main(string[] args)
{
var p = new Program();
try { var task1 = p.GetToken1WithoutAsync(); }
catch( Exception ) { Console.WriteLine("Throws before await.");};
var task2 = p.GetToken2WithAsync(); // Does not throw;
try { var token2 = await task2; }
catch( Exception ) { Console.WriteLine("Throws on await.");};
var task3 = p.GetToken3WithAsync(); // Does not throw;
try { var token3 = await task3; }
catch( Exception ) { Console.WriteLine("Throws on await.");};
var task4 = p.GetToken4WithAsync(); // Does not throw;
try { var token4 = await task4; }
catch( Exception ) { Console.WriteLine("Throws on await.");};
}
// .NETCoreApp,Version=v3.0
Throws before await.
Throws on await.
Throws on await.
Throws on await.
(Cross post of my answer for When async Task<T> required by interface, how to get return variable without compiler warning)
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.
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; //
} // }
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.