Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble understanding async and await

I've been trying to understand async/await and Task in C# but have been failing spectacularly despite watching youtube videos, reading documentation and following a pluralsight course.

I was hoping someone might be able to help answer these slightly abstract questions to help my brain out.

1.Why do they say that async/await enables an 'asynchonrous' method when the async keyword on it's own does nothing and the await keyword adds a suspension point? Isn't adding a suspension point forcing the method to act synchronously, i.e. finish the task marked by the await before moving on.

2.Apparently you are not supposed to use async void except for event handlers, so how do you call an async method normally? It seems that in order to call an async method by using the await keyword, the method/class that is calling it itself needs to be marked as async. All the examples I've seen have 'initiated' an async void method with an event handler. How would you 'escape' this wrapping of async/await to run the method?

3.

public async Task SaveScreenshot(string filename, IWebDriver driver)
{
    var screenshot = driver.TakeScreenshot();
    await Task.Run(() =>
    {
        Thread.Sleep(2000);
        screenshot.SaveAsFile(filename, ScreenshotImageFormat.Bmp);
        Console.WriteLine("Screenshot saved");
    });
    Console.WriteLine("End of method");
}

Relating back to 1. this looks like a synchronous method. Execution pauses when it gets to Task.Run, therefore Console.WriteLine("End of method"); will not be executed until the task is finished. Maybe the whole method itself will be executed asynchronously at the point it is triggered in the code? But relating back to 2, you need to call this with an await otherwise you get the message 'Because this call is not awaited..' therefore adding an await will cause that execution point to be synchronous and so on and so.

Any help understanding this would be much appreciated.

like image 325
Konzy262 Avatar asked Jun 02 '17 17:06

Konzy262


3 Answers

Isn't adding a suspension point forcing the method to act synchronously, i.e. finish the task marked by the await before moving on.

No, the word you're thinking of is "sequential", not "synchronous". await results in asynchronous sequential code. "Sequential" meaning "one at a time"; "synchronous" meaning "blocking until completed".

how do you call an async method normally?

Using await.

How would you 'escape' this wrapping of async/await to run the method?

Ideally, you don't. You go async all the way. Modern frameworks (including ASP.NET MVC, Azure Functions / WebJobs, NUnit / xUnit / MSTest, etc) all allow you to have entry points that return Task. Less-modern frameworks (including WinForms, WPF, Xamarin Forms, ASP.NET WebForms, etc) all allow async void entry points.

So, ideally you do not call asynchronous code from synchronous code. This makes sense if you think about what asynchronous code is: the entire point of it is to not block the calling thread, so if you block the calling thread on asynchronous code, then you lose all the benefits of asynchronous code in the first place.

That said, there are rare situations where you do need to treat the code synchronously. E.g., if you are in the middle of a transition to async, or if you are constrained by a library/framework that is forcing your code to be synchronous and won't work with async void. In that case, you can employ one of the hacks in my article on brownfield async.

like image 187
Stephen Cleary Avatar answered Oct 22 '22 17:10

Stephen Cleary


Your understanding is pretty good :). The main point you seem to be missing is that "asynchronous" methods in .NET mean methods that can stop execution without blocking the calling thread.

As you pointed out in (1), the async keyword basically enables the use of await and requires the return type to be void or Task/Task<T>. await just instructs the current method to suspend execution until the task is complete.

What you are missing here is that it suspends just the current method. It does not block the thread the method was executing on. This is important in cases like the UI thread of a WPF application. Suspend method execution and everything keeps running, block the thread and the application stops responding.

You usually want your async calls to go all the way to the top (like an event handler), this allows the most flexibility and prevents deadlock situations. However; you can wait for a Task returning method to complete with Wait:

someAsyncMethod.Wait()

Or get the return value:

 var result = someAsyncMethod.Result;

Note that both of these are synchronous and block the calling thread. Doing this can cause deadlock if the async task is waiting for some other work on the calling thread to complete.

The above should answer your question in (3); the method itself appears to execute synchronously (this is the magic of await/async) but the task doesn't block the calling thread.

like image 40
BradleyDotNET Avatar answered Oct 22 '22 18:10

BradleyDotNET


It is asynchronous because you don't have to wait the method to return. In your code, you may call the async method and save the task in a variable. Continue doing something else. Later, when the method result is needed, you await the response (task).

// Synchronous method.
static void Main(string[] args)
{
    // Call async methods, but don't await them until needed.
    Task<string> task1 = DoAsync();
    Task<string> task2 = DoAsync();
    Task<string> task3 = DoAsync();

    // Do other stuff.

    // Now, it is time to await the async methods to finish.
    Task.WaitAll(task1, task2, task3);

    // Do something with the results.
    Console.WriteLine(task1.Result);
    Console.ReadKey();
}

private static async Task<string> DoAsync()
{
    Console.WriteLine("Started");
    await Task.Delay(3000);
    Console.WriteLine("Finished");

    return "Success";
}

// Output:
// Started
// Started
// Started
// Finished
// Finished
// Finished
// Success
like image 27
Rodris Avatar answered Oct 22 '22 17:10

Rodris