0

I have some code in here. This is simplified version of a real class:

public class Delayer
{
    //it has to be unawaitable
    public async void Execute(Action action)
    {
        await Task.Delay(10).ConfigureAwait(false);
        action.BeginInvoke(null, null); //action.Invoke();
    }
}

I use it:

private static Task TestFoo()
{
    throw new Exception();
}


delayer.Execute(async () =>
{
    //do something else
    await TestFoo().ConfigureAwait(false);
});

I can't hadle this exception by passing Execute method into try/catch and I can't do it by passing action.BeginInvoke(null, null) into try/catch as well. I can handle it if only I surround async lambda with try/catch when pass it to Execute method.

My question is: why is async lambda executed with await? Because if it weren't executed with await, exception would be swallowed.

I want Execute method to swallow all exceptions thrown from an action. Any ideas how to do it? What do I do wrong?

Addition:

The behavior of Execute must be like "just a fire and forget operation".

1
  • implement try catch with in execute method only and handle all the exceptions there. Commented Mar 1, 2018 at 8:38

1 Answer 1

1

Edit

If your really, really want a Fire and Forget method the only thing to do is to catch all exceptions in the Execute method. But you have to accept an awaitable task if you want to be able to catch exceptions instead of using BeginInvoke on a non-awaitable Action.

public class Delayer
{
    public async Task Execute(Func<Task> action) // or public async void Execute(Func<Task> action) if you insist on it.
    {
        try
        {
            await Task.Delay(10).ConfigureAwait(false);
            await action.Invoke();
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

you can then safely do

void CallDelayedMethod()
{
    var delayer = new Delayer();

    delayer.Execute(ThrowException);
}

public Task ThrowException()
{
    throw new Exception();  
}

I would still return a Task and leave it to the caller to ignore it by not awaiting it (fire and forget) or not.

Original answer

You are not following the best practices by using an async void signature in the class Delayer.

public async void Execute(Action action)

should be

public async Task Execute(Action action)

so you can await the call to Execute. Otherwise it is just a fire and forget operation and that makes catching exceptions difficult. By making it awaitbale you can do:

try
{
    await Delayer.Execute(...);
}
catch(Exception ex)
{
    ....
}

From the best practices:

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.

Also, you should have Execute accept a Task if you want to pass awaitable actions to it:

public async Task Execute(Func<Task> action)
{
    await Task.Delay(10).ConfigureAwait(false);
    await action.Invoke();
}
Sign up to request clarification or add additional context in comments.

4 Comments

The main idea of this class is " just a fire and forget operation". So that Execute has to be unawaitable.
@DdarkSideE Ah, you did not mention that in the question. It is also a very common mistake. But I edited my answer
Is it possible to make Execute take both of Action and Func<Task> without overloading Execute and without wrapping Action with Func<Task> ?
Hmm I don't think so. But having an overload is not that bad or can't you modify that class?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.