and has 2 comments
My colleague showed me today something that I found interesting. It involves (sometimes unwittingly) casting an awaitable method to Action. In my opinion, the cast itself should now work. After all, an awaitable method is a Func<Task> which should not be castable to Action. Or is it? Let's look at some code:
var method = async () => { await Task.Delay(1000); };
This does not work, as compilation fails with Error CS0815 Cannot assign lambda expression to an implicitly-typed variable, which means we need to set the type explicitly. But what is it? It receives no parameter and returns nothing. So it must be an Action, right? But it is also an async/await method, which means it's a Func<Task>. Let's try something else:
Task.Run(async () => { await Task.Delay(1000); });
This compiles. If we hover or go to implementation for the Task.Run method, we reach the public static Task Run(Func<Task> function); signature. So that does it, right? It IS a Func<Task>! Let's try something else, though.
Action action = async() => { await Task.Delay(1000); };
Task.Run(action);
This compiles again! So it IS an Action, too!

What is my point, though? Consider you would want to create a method that receives an Action as a parameter. You want something done, then to execute the function, something like this:
public void ExecuteWithLog(Action action)
{
Console.WriteLine("Start");
action();
Console.WriteLine("End");
}

And then you want to use it like this:
ExecuteWithLog(async () => {
Console.WriteLine("Start delay");
await Task.Delay(1000);
Console.WriteLine("End delay");
});

The output will be:
Start
Start delay
End
End delay
There is NO WAY of awaiting the original method in the ExecuteWithLog method, as it is received as an Action, and while it waits for a second, execution returns to ExecuteWithLog immediately. Write the method like this:
public async void ExecuteWithLog(Func<Task> action)
{
Console.WriteLine("Start");
await action();
Console.WriteLine("End");
}
and now the output is as expected:
Start
Start delay
End delay
End

Why does this happen? Well, as mentioned above, you start with an Action, then you need to await some method (because now everybody NEEDS to use await/async), and then you get an error that your method is not marked with async. Now it's suddenly something else, not an Action anymore. Perhaps that would be annoying, but this ambiguity in defining what an anonymous async parameterless void method is worse.

Comments

Siderite

Very nice connection! Yes, it is related to the void async method issue. Yet, in the post I described a way to get to an async method returning void without actually meaning to: you just wanted a simple action that happened to execute some async code and you just fixed the code warnings. I think a rule that warns of void asyncs is a good thing, as a way of letting people know they got into this mess.

Siderite

Alin

Avoid Async Void - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Alin

Post a comment