1

Lets suppose we have an action execution wrapper like this:

private void ExecuteAction(Action action) { Console.WriteLine($"starting action ..."); action(); Console.WriteLine($"finished action ..."); } 

Lets suppose we call it like this:

ExecuteAction( async () => { ... } ); // 1 

or like this:

ExecuteAction( () => { ... } ); // 2 

I wonder what is the difference between the two call and how to handle inside the ExecuteAction() method? Is there any difference?

10
  • 1
    What do you mean by "how to handle"? Do you want to differentiate between the two? I don't think you can. Commented May 23, 2023 at 5:36
  • The difference is, that async keyword makes the lambda function asynchronous. It allows you to use the await keyword within that function. Commented May 23, 2023 at 5:40
  • 1
    That would translate to async void which (with exceptions) is a well- known anti pattern. You may prefer Func<Task> to make it awaitable. Commented May 23, 2023 at 5:41
  • 1
    @Musti that's a common misconception. async doesn't make it asynchronous. It still could be executed synchronously. Commented May 23, 2023 at 5:44
  • 1
    @Fildor that's true, should've better expressed it as "allows it to be executed async". Commented May 23, 2023 at 5:46

1 Answer 1

6

To use an Action delegate here would translate to async void which is a well-known anti-pattern (with exceptions).

See, the async keyword is not part of the signature. It is an implementation detail, that allows the use of await inside the method.

So, for ExecuteAction it does not make a difference. All it sees is a void Action() delegate. And using it like ExecuteAction( async () => { await SomethingAsync(); }); comes with all the pitfalls and downsides layed out in the article, I linked above. (tl;dr: really not recommended to do)

See also docs on the keyword itself:

void. async void methods are generally discouraged for code other than event handlers because callers cannot await those methods and must implement a different mechanism to report successful completion or error conditions.

- async - C# Reference

If you want to offer both sync and async experiences to your audience, you could do something like:

// your already known implementation private void ExecuteAction(Action action) { // ... } /// <summary>Async Version</summary> /// <example> /// Usage: /// <code> /// await ExecuteAsync( () => { ... } ); /// </code> /// or /// <code> /// await ExecuteAsync( async () => { ... await SomethingAsync(); ... } ); /// </code> /// </example> private async Task ExecuteAsync(Func<Task> asyncAction) { // Before client-defined function code goes here await asyncAction(); // After client-defined function code goes here } 

If you need to, you can also pass a state object and avoid it being captured:

/// <example> /// <code> /// await ExecuteAsync( o => { ... }, someStateObject ); /// </code> /// </example> private async Task ExecuteAsync(Func<object,Task> asyncAction, object state = null) { await asyncAction(state); } 

Providing async API, I'd also always give the opportunity to pass a CancellationToken:

/// <example> /// <code> /// await ExecuteAsync( o => { ... }, someStateObject, cancel ); /// </code> /// </example> private async Task ExecuteAsync(Func<object, CancellationToken, Task> asyncAction, object state = null, CancellationToken token = default) { await asyncAction(state, token); } 

If this is GUI-related, you may also consider reporting progress:

/// <example> /// <code> /// await ExecuteAsync( o => { ... }, someStateObject, p => { ... },cancel ); /// </code> /// </example> private async Task ExecuteAsync<TProgress>( Func<object, IProgress<TProgress>, CancellationToken, Task> asyncAction, object state = null, IProgress<TProgress> progress = null, CancellationToken token = default) { await asyncAction(state, progress, token); } 

And of course you can make your state object type-safe

private async Task ExecuteAsync<TState, TProgress>( Func<TState, IProgress<TProgress>, CancellationToken, Task> asyncAction, TState state = null, IProgress<TProgress> progress = null, CancellationToken token = default) { await asyncAction(state, progress, token); } 

Sources:

Async/Await - Best Practices in Asynchronous Programming # Avoid Async Void - Stephen Cleary, March 2013, MSDN Magazine

async - C# Reference

Sign up to request clarification or add additional context in comments.

2 Comments

Thank for the very detailed answer! But I lost somewhere... so you suggest not to write async () => or you suggesting create to different ExecuteAction() implementations: one for a non-async actions, and one for the async ones?
Exactly. You just have to name them differently, because they have different return types. But, yes: Have one thing for Sync and one thing for Async.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.