Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Do async lambdas return Tasks?

5.00/5 (4 votes)
18 May 2016CPOL2 min read 12.3K  
The TL;DR; version is: Sometimes. The more important question is how you ensure that you generate the method call you want. Let’s start with a bit of background. Lambda expressions do not have types. However, they can be converted into any compatible delegate type.

The TL;DR; version is:

Sometimes.

The more important question is how you ensure that you generate the method call you want. Let’s start with a bit of background. Lambda expressions do not have types. However, they can be converted into any compatible delegate type. Take these two declarations as a starting point:

Action task = async () => await Task.Yield();
Func<Task> task2 = async () => await Task.Yield();

Notice that this lambda body can be assigned to either an Action or a Func<Task>. The lambda expression can represent either an async void method, or a Task returning async method.

Well, let’s suppose you call Task.Run with that lambda body:

Task.Run(async () => await Task.Yield());

(Ignore for a moment the obvious uselessness of calling Task.Run and telling it to yield.) Which of the following overloads does that Lambda resolve to:

public static Task Run(Action action);
public static Task Run(Func<Task> action);

They correspond to the two delegate declarations used in the first code sample above. This call compiles, so the compiler must find one of them to be a better method. Which one?

The compiler prefers the method call that expects a delegate that returns a Task. That’s good, because it’s the one you’d rather use. The compiler comes to this conclusion using the type inference rules about the return value of anonymous delegates. The “inferred return type” of any async anonymous function is assumed to be a Task. Knowing that the anonymous function represented by the lambda returns a Task, the overload of Task.Run() that has the Func<Task> argument is the better match.

The C# language overload rules, along with the rules for type inference for async lambda expressions ensures that the preferred overload generates a Task returning async method.

So, what does that mean for me?

Remember that async void methods are not recommended. They are fire-and-forget methods, and you can’t observe any errors that might occur in the async method. You want to avoid accidentally creating an async void lambda expression.

There are two recommendations that come from these rules in the language specification.

First, avoid using async lambdas as arguments to methods that expect Action and don’t provide an overload that expects a Func<Task>. If you do that, you’ll create an async void lambda. The compiler will happily assume that’s what you want.

Second, if you author methods that take delegates as arguments, consider whether programmers may wish to use an async lambda as that argument. If so, create an overload that uses Func<Task> as the argument in addition to Action. As a corollary, create an overload that takes Func<Task<T>> in addition to Task<T> for arguments that return a value.

The language team members worked hard on these overload rules to ensure that in most cases, the compiler prefers Task-returning anonymous functions when you write async lambdas. You have to make sure the right overloads are available.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)