213

I'm writing a WinForms application that transfers data to a USB HID class device. My application uses the excellent Generic HID library v6.0 which can be found here. In a nutshell, when I need to write data to the device, this is the code that gets called:

private async void RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    }
}

When my code drops out of the while loop, I need to read some data from the device. However, the device isn't able to respond right away so I need to wait for this call to return before I continue. As it currently exists, RequestToGetInputReport() is declared like this:

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

For what it's worth, the declaration for GetInputReportViaInterruptTransfer() looks like this:

internal async Task<int> GetInputReportViaInterruptTransfer()

Unfortunately, I'm not very familiar with the workings of the new async/await technologies in .NET 4.5. I did a little reading earlier about the await keyword and that gave me the impression that the call to GetInputReportViaInterruptTransfer() inside of RequestToGetInputReport() would wait (and maybe it does?) but it doesn't seem like the call to RequestToGetInputReport() itself is waiting because I seem to be re-entering the while loop almost immediately?

Can anyone clarify the behavior that I'm seeing?

7 Answers 7

319

The most important thing to know about async and await is that await doesn't wait for the associated call to complete. What await does is it returns the result of the operation immediately and synchronously if the operation has already completed or, if it hasn't, it schedules a continuation to execute the remainder of the async method and then returns control to the caller. When the asynchronous operation completes, the scheduled completion will then execute.

The answer to the specific question in your question's title is to block on an async method's return value (which should be of type Task or Task<T>) by calling an appropriate Wait method:

public static async Task<Foo> GetFooAsync()
{
    // Start asynchronous operation(s) and return associated task.
    ...
}

public static Foo CallGetFooAsyncAndWaitOnResult()
{
    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;
}

In this code snippet, CallGetFooAsyncAndWaitOnResult is a synchronous wrapper around asynchronous method GetFooAsync. However, this pattern is to be avoided for the most part since it will block a whole thread pool thread for the duration of the asynchronous operation. This an inefficient use of the various asynchronous mechanisms exposed by APIs that go to great efforts to provide them.

The answer at "await" doesn't wait for the completion of call has several, more detailed, explanations of these keywords.

Meanwhile, @Stephen Cleary's guidance about async void holds. Other nice explanations for why can be found at http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ and https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

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

8 Comments

I find it useful to think (and talk) about await as an "asynchronous wait" - that is, it blocks the method (if necessary) but not the thread. So it makes sense to talk about RequestToSendOutputReport "waiting for" RequestToGetInputReport even though it's not a blocking wait.
@Richard Cook - thank you very much for the additional explanation!
This ought to be the accepted answer, since it more clearly answer the actual question (i.e. how to thread-wise block on an async method).
best solution is wait async till task complete is var result = Task.Run(async() => { return await yourMethod(); }).Result;
@DavidKlempfner: Wait and Result were already on the Task type before await was invented. Before await arrived, Task was part of the Task Parallel Library, and primarily used for parallel programming.
|
165

Avoid async void. Have your methods return Task instead of void. Then you can await them.

Like this:

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    }
}

private async Task RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

6 Comments

Very nice, thank you. I was scratching my head on a similar issue and the difference was to change void to Task just as you had said.
It's a minor thing, but to follow the convention both methods should have Async added to their names, e.g. RequestToGetInputReportAsync()
and what if the caller is the Main function?
@symbiont: Then use GetAwaiter().GetResult()
@AhmedSalah The Task represents the execution of the method - so return values are placed on Task.Result, and exceptions are placed on Task.Exception. With void, the compiler has nowhere to place exceptions, so they are just re-raised on a thread pool thread.
|
143

Best Solution to wait AsynMethod till complete the task is

var result = Task.Run(async() => await yourAsyncMethod()).Result;

8 Comments

Or this for your async "void": Task.Run(async () => { await yourAsyncMethod(); }).Wait();
What is the benefit of this over yourAsyncMethod().Result?
Simply accessing the .Result property doesn't actually wait until the task is finished executing. In fact, I believe it throws an exception if it is called before a task is completed. I think the advantage of wrapping this in a Task.Run() call is that, as Richard Cook mentions below, "await" does not actually wait for a task to complete, but using a .Wait() call blocks your whole thread pool. This allows you to (synchronously) run an async method on a separate thread. Slightly confusing, but there it is.
Quick reminder ECMA7 featurelike assync() or await wont work in pre-ECMA7 environment.
@Lightning3 Should one still wrap the async method in a Task.Run() call? Or just go for .GetAwaiter().GetResult() without the wrapper? What would be the difference in these two approaches? Furthermore, why not Task.Run().Result?
|
10

just put Wait() to wait until task completed

GetInputReportViaInterruptTransfer().Wait();

3 Comments

This blocks the current thread. So this is usually a bad thing to do.
And sometimes it is exactly what you need
And will sometimes not only block, but also dead-lock your app (especially in WPF when using the dispatcher/main-thread)
1

All of the above answers are right, you should never synchronously wait on a task ... unless you have to! Sometimes you want to call an async method inside of a interface implementation you do not control and there is no way to do "async all the way down."

Here is a little class to at least contain the damage:

public class RunSynchronous
{
    public static void Do(Func<Task> func) => Task.Run(func).GetAwaiter().GetResult();
    public static T Do<T>(Func<Task<T>> func) => Task.Run(func).GetAwaiter().GetResult();
    public static void Do(Func<ValueTask> func) => Do(() => func().AsTask());
    public static T Do<T>(Func<ValueTask<T>> func) => Do(() => func().AsTask());
}

This class uses the .GetAwaiter.GetResult pattern to actually convert async to sync. But if you do that byitself in WPF or another SynchronizationContext bound to a particular thread you deadlock. This code avoids deadlock by transferring the async operation to the threadpool which is not synchronized to a particular thread. As long as you do not go crazy and block all the threadpool threads you ought to be OK.

usage is like this

    return RunSynchronous.Do(()=>AsyncOperation(a,b,c));

will launch AsynchronousOperation(a,b,c) on the threadpool and wait for it to return. As long as you do not explicitly synchornize back to the origin thread you ought to be OK.

Comments

-5

The following snippet shows a way to ensure the awaited method completes before returning to the caller. HOWEVER, I wouldn't say it's good practice. Please edit my answer with explanations if you think otherwise.

public async Task AnAsyncMethodThatCompletes()
{
    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() => { }); // <-- This line here, at the end
}

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")

2 Comments

Downvoters, care to explain, like I asked in the answer? Because this works well as far as I know...
The problem is that the only way you can do the await + the Console.WriteLine is by it becoming a Task, which gives up control between the two. so your 'solution' will ultimately yield a Task<T>, which doesnt address the problem. Doing a Task.Wait will will actually stop processing (with deadlock possibilities etc). In other words, await does not actually wait, it simply combines two asynchronously executable portions into a single Task (that someone can watch or wait for)
-7

Actually I found this more helpful for functions that return IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;

Comments

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.