Here is my test:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace asynktest
{
class Program
{
static async Task Main(string[] args)
{
var t = Test1(true);
await t; //throws InvalidOperationException here. correct
t = Test1(false);
await t; //throws NotImplementedException here. correct
t = Test2(true); //throws InvalidOperationException here. wrong
await t;
t = Test2(false); //throws NotImplementedException here. wrong
await t;
t = Test3(true);
await t; //throws InvalidOperationException here. correct
t = Test3(false); //throws NotImplementedException here. wrong
await t;
t = Test4(true);
await t; //throws InvalidOperationException here. correct
t = Test4(false);
await t; //throws NotImplementedException here. correct
t = Test5(true);
await t; //throws InvalidOperationException here. correct
t = Test5(false);
await t; //throws NotImplementedException here. correct
}
public static async Task<int> Test1(bool err) //CS1998: This async method lacks 'await'
{
if (err)
throw new InvalidOperationException();
return GetNum(42);
}
public static Task<int> Test2(bool err)
{
if (err)
throw new InvalidOperationException();
return Task.FromResult(GetNum(42));
}
public static Task<int> Test3(bool err)
{
if (err)
return Task.FromException<int>(new InvalidOperationException());
return Task.FromResult(GetNum(42));
}
public static async Task<int> Test4(bool err)
{
await Task.CompletedTask; // remove CS1998
if (err)
throw new InvalidOperationException();
return GetNum(42);
}
public static Task<int> Test5(bool err)
{
try
{
if (err)
return Task.FromException<int>(new InvalidOperationException());
return Task.FromResult(GetNum(42));
}
catch (Exception e)
{
return Task.FromException<int>(e);
}
}
public static int GetNum(int num)
{
throw new NotImplementedException();
}
}
}
Test1 generate the warning I want to fix. Only Test4 and Test5 do not change program flow. But Test5 require a terrible amount of boilerplate code. Can it really be that the alternatives are to sprinkle "await Task.CompletedTask" all over my program or add crazy amount of silly Task.FromX code? At this point I really feel that CS1998 is plain wrong and must be silenced. Or am I missing something?
Edit: removing Task as result is not an option, it is typically an interface (that I cannot control) implementation.
Edit2: program flow is the key here. Changing a program to throw exceptions at different places can't be good. Imagine a program that create 3 tasks, shuts down a nuclear reactor, then Task.WaitAll(t1, t2, t3). Leaving the code as is or Test4\Test5, else the reactor explodes:-)
Edit3: I have often read that async create an unnecessary state machine (performance), but from my example you can see this is not quite true. Without this state machine, it will change program flow. Off course this must be in relation to a forced async interface implementation, else you would just make it synch, and I guess here CS1998 can be helpful:-) But CS1998 is not clever enough to understand if this is code you have control over or not. Tricky...
Edit4: I ended up with ignoring the warning, but the answer with the wrappers is a very good alternative. But I wish Microsoft could make a complementing "sync" keyword that could auto generate these wrappers in addition to generating a state machine if "async".