bwall
bwall

Reputation: 1060

Async void lambda expressions

A quick google search will tell you to avoid using async void myMethod() methods when possible. And in many cases there are ways to make it possible. My question is basically an offshoot of this best practice:

What does the lambda expression below evaluate to?

Task.Run( async ()=> await Task.Delay(1000));

If it becomes an async Task then we are following best practice.

But what if it evaluates to async void?

Upvotes: 20

Views: 13807

Answers (3)

afruzan
afruzan

Reputation: 1698

As Documention sayed, beginning with C# 10, you can specify the return type of a lambda expression before the input parameters.

var fn = async Task (string p1, int p2) =>
{
    // await something
};

In case of Task.Run(..) refer to Gabriel Luci answer. But I usually specify the Task explicitly to be clear specially in asp.net core Map route methods.

Upvotes: 1

Gabriel Luci
Gabriel Luci

Reputation: 41008

The documentation for expression lambdas says,

An expression lambda returns the result of the expression

So, for example, () => "hi" returns a string, even though there is no return statement. But if the expression doesn't return anything, like in () => Console.WriteLine("hi"), then it's considered void.

However there is a bit of trickery with async lambdas. The expression await Task.Delay(1000) doesn't really return anything in itself. However, the language can figure out that if you have an async lambda, you likely want it to return a Task. So it will prefer that.

So this:

Task.Run(async () => await Task.Delay(1000));

Is equivalent to this, if you were to express it with a named method:

private async Task Wait1000() {
    await Task.Delay(1000);
}
Task.Run(Wait1000);

But it is important to note that async lambdas can be inferred to be async void. The only reason it is considered async Task here is because Task.Run has an overload for Func<Task>. If the only available overload took an Action parameter, then it would be inferred to be async void, without any warning to you.

For example, this produces no error and the lambda is treated as async void:

private void RunThisAction(Action action) {
    action();
}
RunThisAction(async () => await Task.Delay(1000));

That is different than if you passed it a named async Task method, which would cause a compiler error:

private void RunThisAction(Action action) {
    action();
}
private async Task Wait1000() {
    await Task.Delay(1000);
}
RunThisAction(Wait1000); // 'Task Wait1000()' has the wrong return type

So be careful where you use it. You can always hover over the method name (like the Run in Task.Run) and Visual Studio will tell you which overload it has inferred:

enter image description here

Upvotes: 27

StepUp
StepUp

Reputation: 38209

Yeah, it is evaluated to async Task because Task.Delay(n) has return type of Task. So it is good practice.

In addition, there is msdn example, but it is a little bit more verbose:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

So the above code could be shortened to:

public Form1()
{
    InitializeComponent();
    button1.Click += async (sender, e) =>
    {
        await Task.Delay(1000);
        textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }
}

And now shortened code looks like your code.

Upvotes: 4

Related Questions