Wait for an async void method
Asked Answered
H

8

236

How can I wait for an async void method to finish its job?

For example, I have a function like below:

async void LoadBlahBlah()
{
    await blah();
    //...
}

Now I want to make sure that everything has been loaded before continuing somewhere else.

Haemostat answered 29/11, 2012 at 23:25 Comment(0)
S
351

Best practice is to mark function async void only if it is fire and forget method, if you want to await on, you should mark it as async Task.

In case if you still want to await, then wrap it like so await Task.Run(() => blah())

Shoddy answered 30/11, 2012 at 2:17 Comment(10)
blah is obviously returning task and ha async signature why would you call it without await. even VS will give you warning about it.Theodolite
await Task.Run(() => An_async_void_method_I_can_not_modify_now())Sway
await Task.Run(() => blah()) is misleading. This does not await the completion of async function blah, it just awaits the (trivial) creation of the task, and continues immediately before blah() has completed.Grebe
@JonathanLidbeck the statement "continues immediately before blah is wrong. Try "await Task.Run(() => Thread.Sleep(10_000))", the task is awaited for 10 seconds+ before executing any next lineShoddy
@RohitSharma, that is correct for your example, but Thread.Sleep is not async. This question is about awaiting an async void function, say async void blah() { Task.Delay(10000); }Grebe
@RohitSharma From doc Async void methods can wreak havoc if the caller isn’t expecting them to be async. When the return type is Task, the caller knows it’s dealing with a future operation; when the return type is void, the caller might assume the method is complete by the time it returns. So JonathanLidbeck is right.Cytokinesis
@TanveerBadar we can't start editing all incorrect answers by appending "this is not the correct answer", because then we would have to edit half of the answers in StackOverflow. The correct reaction to an incorrect answer is to downvote it, and upvote a correct answer.Recuperator
To add to TheodorZoulias's comment, you can also post your own answer that you feel is correct/better if no other suitable answers exist and/or comment on other answer(s) to explain what is problematic about them.Ilse
Good luck downvoting an answer with 342 upvotes. :shrug:Echidna
@TanveerBadar currently the answer has 348 upvotes and 6 downvotes. This answer needs more downvotes, because the advice about wrapping the async void method in Task.Run is indeed terrible. Or the author should step in, and remove this suggestion from the answer.Recuperator
V
62

If you can change the signature of your function to async Task then you can use the code presented here

Vikkivikky answered 30/11, 2012 at 0:36 Comment(0)
C
35

The best solution is to use async Task. You should avoid async void for several reasons, one of which is composability.

If the method cannot be made to return Task (e.g., it's an event handler), then you can use SemaphoreSlim to have the method signal when it is about to exit. Consider doing this in a finally block.

Conjugal answered 30/11, 2012 at 2:21 Comment(0)
F
9

I know this is an old question, but this is still a problem I keep walking into, and yet there is still no clear solution to do this correctly when using async/await in an async void signature method.

However, I noticed that .Wait() is working properly inside the void method.

and since async void and void have the same signature, you might need to do the following.

void LoadBlahBlah()
{
    blah().Wait(); //this blocks
}

Confusingly enough async/await does not block on the next code.

async void LoadBlahBlah()
{
    await blah(); //this does not block
}

When you decompile your code, my guess is that async void creates an internal Task (just like async Task), but since the signature does not support to return that internal Tasks

this means that internally the async void method will still be able to "await" internally async methods. but externally unable to know when the internal Task is complete.

So my conclusion is that async void is working as intended, and if you need feedback from the internal Task, then you need to use the async Task signature instead.

hopefully my rambling makes sense to anybody also looking for answers.

Edit: I made some example code and decompiled it to see what is actually going on.

static async void Test()
{
    await Task.Delay(5000);
}

static async Task TestAsync()
{
    await Task.Delay(5000);
}

Turns into (edit: I know that the body code is not here but in the statemachines, but the statemachines was basically identical, so I didn't bother adding them)

private static void Test()
{
    <Test>d__1 stateMachine = new <Test>d__1();
    stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
}
private static Task TestAsync()
{
    <TestAsync>d__2 stateMachine = new <TestAsync>d__2();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

neither AsyncVoidMethodBuilder or AsyncTaskMethodBuilder actually have any code in the Start method that would hint of them to block, and would always run asynchronously after they are started.

meaning without the returning Task, there would be no way to check if it is complete.

as expected, it only starts the Task running async, and then it continues in the code. and the async Task, first it starts the Task, and then it returns it.

so I guess my answer would be to never use async void, if you need to know when the task is done, that is what async Task is for.

Fetishist answered 15/1, 2020 at 15:19 Comment(0)
P
3

You don't really need to do anything manually, await keyword pauses the function execution until blah() returns.

private async void SomeFunction()
{
     var x = await LoadBlahBlah(); <- Function is not paused
     //rest of the code get's executed even if LoadBlahBlah() is still executing
}

private async Task<T> LoadBlahBlah()
{
     await DoStuff();  <- function is paused
     await DoMoreStuff();
}

T is type of object blah() returns

You can't really await a void function so LoadBlahBlah() cannot be void

Permian answered 30/11, 2012 at 1:24 Comment(2)
I want to wait for LoadBlahBlah() to finish, not blah()Haemostat
Unfortunately async void methods do not pause execution (unlike async Task methods)Photoengrave
P
0

do a AutoResetEvent, call the function then wait on AutoResetEvent and then set it inside async void when you know it is done.

You can also wait on a Task that returns from your void async

Pessary answered 23/8, 2013 at 20:34 Comment(0)
R
0

How can I wait for an async void method to finish its job?

The async void methods are not designed to be waited. If you are forced to wait an async void method, meaning that you don't have the option to change the return type of the method from void to Task, you are in trouble. It is not impossible, but you'll have to jump through hoops. The trick is that the SynchronizationContext that is captured when the async void method starts, has its OperationCompleted method invoked automatically when the async void completes. Armed with this knowledge, you can write a class that derives from the SynchronizationContext, and completes a TaskCompletionSource when the OperationCompleted is invoked. Then you'll have to swap the existing SynchronizationContext.Current with your own derivative before invoking the async void method, and swap it back when the async void method returns. Below is a complete implementation:

public static class AsyncVoid
{
    public static Task Run(Action asyncVoidAction)
    {
        SynchronizationContext old = SynchronizationContext.Current;
        Context context = new(old);
        SynchronizationContext.SetSynchronizationContext(context);
        try { asyncVoidAction(); }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(old);
            context.OperationCompleted();
        }
        return context.Completion;
    }

    private class Context : SynchronizationContext
    {
        private readonly SynchronizationContext _old;
        private readonly TaskCompletionSource _tcs = new(
            TaskCreationOptions.RunContinuationsAsynchronously);
        private int _pendingCount = 1;

        public Context(SynchronizationContext old) => _old = old;
        public Task Completion => _tcs.Task;

        public override void Send(SendOrPostCallback d, object state)
        {
            if (_old is not null) _old.Send(d, state); else base.Send(d, state);
        }
        public override void Post(SendOrPostCallback d, object state)
        {
            if (_old is not null) _old.Post(d, state); else base.Post(d, state);
        }
        public override void OperationStarted()
        {
            Interlocked.Increment(ref _pendingCount);
        }
        public override void OperationCompleted()
        {
            if (Interlocked.Decrement(ref _pendingCount) == 0)
                _tcs.TrySetResult();
        }
        public override SynchronizationContext CreateCopy() => new Context(_old);
    }
}

Usage example:

await AsyncVoid.Run(() =>
{
    LoadBlahBlah(); // LoadBlahBlah is an async void method
});

This implementation will wait for the successfull or failed completion of the async void method, but it won't wrap a possible exception in the resulting Task. Any exception will be rethrown as always on the captured synchronization context, causing usually the termination of the process. In other words the async void is still fire-and-crash, this has not changed. In case you want to go a step further and prevent the fire-and-crash behavior, you are entering "implementation details" territory. The exception that is about to crash the process, is passed to the state argument of the Post method as an ExceptionDispatchInfo object. So you could add this code at the top of the Post method:

if (state is ExceptionDispatchInfo edi)
{
    _tcs.TrySetException(edi.SourceException); return;
}

This is not documented, and could easily change in future .NET versions (later than .NET 7). So take this extra step at your own risk.

Recuperator answered 6/9, 2023 at 5:32 Comment(0)
Y
-2

I've read all the solutions of the thread and it's really complicated... The easiest solution is to return something like a bool:

async bool LoadBlahBlah()
{
    await blah();
    return true;
}

It's not mandatory to store or chekc the return value. You can juste do:

await LoadBlahBlah();

... and you can return false if something goes wrong.

Yugoslavia answered 9/5, 2022 at 11:41 Comment(1)
You cannot return anything non-Task like from async methods. Your example won't even compile.Echidna

© 2022 - 2025 — McMap. All rights reserved.