34
votes

I've this method

public void Execute(Action action)
{
    try
    {
        action();
    }
    finally
    {
    }
}

and I need to convert it to an async method call like this one

public async Task ExecuteAsync(Action action)
{
    try
    {
        await action();
    }
    finally
    {
    }
}

The problem with the code above is that the compiler issue the following error

The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.

3
What is the finally doing here? - Jon Hanna
@Jelly I'll follow that link to implement what I want to do. Thank you - vcRobe
@JonHanna What I posted was a snippet of code I think the try...finally was not necessary here. Sorry. - vcRobe
@vcRobe: I have a blog post on async delegates that may clarify this. - Stephen Cleary

3 Answers

49
votes

If you want to await on a delegate, it has to be of type Func<Task> or Func<Task<T>>. An Action is equivalent into a void Action() named method. You can't await on void, yet you can await Task Func() or Task<T> Func:

public async Task ExecuteAsync(Func<Task> func)
{
    try
    {
        await func();
    }
    finally
    {
    }
}

If this can't be done, it means that internally the method isn't truly asynchronous, and what you actually want to do is execute the synchronous delegate on a thread-pool thread, which is a different matter, and isn't really executing something asynchronously. In that case, wrapping the call with Task.Run will suffice.

6
votes

Try this:

public async void ExecuteAsync(Action action)
{
    await Task.Run(action); 
}

Using Task.Run()creates an awaitable by running your action on a different task. And also bear in mind that handling exceptions on "awaitables" does not work as intended.

Better wrap that action() call in a try catch an do Task.Run() on that wrapper.

1
votes

Let's simplify your starting point down to:

public void Execute(Action action)
{
  action();
}

Because you say the finally isn't important.

Valid but pointless:

public async Task ExecuteAsync(Action action)
{
    action();
}

This will be pretty much the same as doing:

public Task ExecuteAsync(Action action)
{
  action();
  return Task.FromResult(0);
}

That is, it'll do what the non-async was doing, and then return a "completed" Task so nothing is gained. It's worth noting though as it's often a valid part-way-point in moving from non-async to async.

Better:

public async Task ExecuteAsync(Action action)
{
  await Task.Run(() => action()); 
}

Which in this case, because it's a single void-returning call can be simplified to:

public async Task ExecuteAsync(Action action)
{
  await Task.Run(action); 
}

Whether this is worth doing or not is another matter. This releases the current thread from being used, but transfers to another thread to do the work. If we're just going to await the result of this when it's called then we might as well just call the non-async version and be done with it. If however we're doing WaitAll in the caller, or something else that hence benefits from this, then it could indeed be useful.

Potentially much better though is:

public async Task ExecuteAsync(Action action)
{
  await actionAsync();
}

Here there's an Async version of the method we are calling, so we change to make use of that. Now, that could be just the same as the above if actionAsync just spins up a thread or uses the thread pool. If however actionAsync does something using asynchronous I/O then there's a much bigger benefit to this.

Note that in this case we could have just tail-called the Task we get:

public Task ExecuteAsync(Action action)
{
  return actionAsync();
}

However, that wouldn't be the same if we needed something done after an await within our method. E.g.:

public void Execute(Action action)
{
  action();
  otherAction();
}

Would have to become:

public async Task Exectute(Action action)
{
  await actionAsync();
  await otherActionAsync();
}

Or if otherAction had no async version:

public async Task Exectute(Action action)
{
  await actionAsync();
  otherAction();
}