Top 20+ Async/Await C# Interview Questions and Answers

Async and await in C# are the code markers, which marks code positions from where the control should resume after a task completes. Let’s start with practical examples for understanding the programming concept.

In 2012, C#5 was released. This version introduced two new keywords async and await. At that time CPU clock speed reached an upper limit imposed by physical laws. But chip makers started to deliver CPU with several cores that can run tasks in parallel. Thus C# needed a away to ease asynchronous programming.

The async and await keywords make asynchronous programming almost too easy. Many programmers use them often without really understanding the runtime workflow. This is a great thing, they can focus more on the business of their applications and less on asynchronous details. But some disconcerting behaviors might (and will) happen. Thus it is preferable that one understands the logic behind async and await and what can influence it. This is the goal of the present article.

Here is a small C# program that illustrates the async and await keywords. Two tasks A and B runs simultaneously. Task A runs within a method marked as async, while B is executed after calling the async method.

Before explaining in details how the two occurrences of the keyword async modify the workflow, let’s make some remarks:

First let’s explain the easy role of the async keyword. Then we’ll have a closer look at the influence of the await keyword.

C# Async / Await – Make your app more responsive and faster with asynchronous programming

Q: What is the purpose of async/await keywords?

These keywords allow writing asynchronous non-blocking code in a synchronous fashion.

This feature is facilitated by the Task/Task<T> classes or ValueTask/ValueTask<T> structs. These types represent an abstraction around an operation that may execute asynchronously.

We use await keyword to materialize the task into resulting value. Methods that contain await must be marked with the async keyword.

Q: What’s the difference between asynchronous programming and multithreaded programming?

An asynchronous task does not necessarily represent execution on a separate thread. You can think of an asynchronous operation as just an aggregation of two events — start and finish.

A good example of an asynchronous operation is reading a file from a hard drive. To read individual bytes of a file, operating system issues requests to the driver software, which in turn tells the drive to seek to a specific position by moving its mechanical head. The process of moving the head around is asynchronous, it’s not an operation that runs on CPU, it’s just a physical task you have to wait for completing. This is the type of “pure” async operation that can be represented with the async/await pattern.

That said, an asynchronous task may also represent some CPU-bound calculation happening on a separate thread, but this is an implementation detail. This is useful when you want to delegate execution to a different thread in order to not block the calling thread, while disguising it as an asynchronous operation. You can do that by calling Task.Run().

Overall, it’s fair to say that every multithreaded execution can be represented as an asynchronous operation, but not every asynchronous operation necessarily employs additional threads.

Q: How does it work?

Let’s take the following method as an example:

public async Task DoAsync()
{
    Console.WriteLine("Before await");

    await Task.Delay(TimeSpan.FromSeconds(1));

    Console.WriteLine("Between awaits");

    await Task.Delay(TimeSpan.FromSeconds(1));

    Console.WriteLine("After await");
}
Functionally, this code works by printing “Before await” to the console, waiting 1 second, printing “Between awaits”, waiting 1 second again, and then printing “After await”.

Everything until the first await is executed synchronously just like it would in a normal method. That means “Before await” will be printed on the same context that called this method.

The next thing that happens is that we create and run a new task using a static helper method Task.Delay(). This is a simple task that does nothing and automatically turns into completed after a specified delay.

Upon reaching the await keyword, the runtime will return control back to the calling method, which may or may not await it. If it does await, the same happens again, returning control to the caller of that method in turn, until it reaches a method in the call stack that either does not await (usually event handlers on a message loop thread) or materializes the task synchronously (e.g. entry point in a console application).

Once the task completes (after 1 second), the execution is returned back to our method, which continues by printing “Between awaits”. By default, the execution will continue on the same context as the one that started executing this method.

Then the story repeats again by running and waiting on a new task, finally printing “After await”. After the last message, the execution is returned to the caller method (if it awaited on DoAsync) so that it can continue executing further.

Q: What happens if we execute an asynchronous method but don’t await it?

Nothing special, the operation represented by the task will continue its lifetime normally, but the result won’t be observed. The task object itself will eventually be reclaimed by garbage collector.

Note that we can also use .ContinueWith() to handle the result in a call-back fashion.

Q: What happens if an exception is thrown within an asynchronous method?

If the method is awaited, the exception will instantly propagate to the calling method, then to the caller of that method, and so on, as long as the whole chain is awaited.

Otherwise, the exception will be considered unobserved, which can lead (in some versions of the framework) to the application crashing as soon as the task is disposed by the finalizer.

Q: Is it possible to make a lambda that executes asynchronously?

Yes.

var result = await new Func<Task>(async () => await Task.Delay(100));
Q: What happens when a method returns a Task without awaiting it?

Example:

public Task WaitAsync() => SomeOtherMethodAsync();
In this case, if an exception is thrown within asynchronous part of SomeOtherMethodAsync(), the WaitAsync() method will not be listed in the stack trace.

Q: Task type implements IDisposable, when are we supposed to dispose tasks?

The Dispose() method is not supposed to be invoked manually, a task will be disposed automatically when you await it or when it’s reclaimed by the GC.

Q: What is the purpose of ConfigureAwait()?

By default, after the awaited task is completed, the execution continues on the originally captured context, i.e. the same thread that invoked the method. You can override that behavior by specifying ConfigureAwait(false), indicating that the execution may continue on a different context.

It’s generally recommended using ConfigureAwait(false) wherever possible as it can offer minor performance gain and help prevent deadlocks.

Q: Why is the default behavior to continue on the captured context?

For compatibility reasons, due to constraints when working with Windows UI and classic ASP.NET applications. In one you can only interact with controls from the main thread, in the other you can only serve the response on the same thread that handled the request, so it was important that the execution continued on the captured context.

Q: What is the difference between Task and ValueTask?

The main difference is that Task is a class while ValueTask is a struct. Latter was added to the BCL to alleviate unnecessary pressure on the garbage collector caused by asynchronous methods that often return synchronously (e.g. cached result). You can think of ValueTask as a discriminated union of Task or a synchronous result. Since recently, ValueTask can also represent a result signaled by ValueTaskSource.

Q: What are the main downsides of using asynchronous methods compared to synchronous methods?

Asynchronous methods can be harder to debug, especially because exceptions thrown from asynchronous methods have difficult to read stack traces. Also, running many asynchronous tasks in a tight loop can put pressure on the garbage collector.

1. Can you explain what asynchronous programming is?

Asynchronous programming is a form of programming that allows for tasks to be completed out of order. This means that a program can start a task and then move on to other tasks before the first one is completed. This can be helpful in situations where a task might take a long time to complete, but the program doesn’t need to wait for it to finish before moving on.

2. How does async/await help with performance and scalability?

Async/await can help improve performance and scalability by allowing your application to do other work while it is waiting for a task to complete. This can help avoid bottlenecks and keep your application responsive. Additionally, using async/await can help reduce the overall amount of code needed to be written, making your application easier to maintain.

3. Can you explain the difference between an async function and a regular function in JavaScript?

Async functions are functions that allow you to use the await keyword to wait for a promise to resolve before continuing execution of the function. Regular functions do not have this ability, and will instead execute the code inside of them immediately.

4. What are some of the advantages of using async functions in JavaScript?

Async functions help to make code simpler and easier to read. They also can make code execution more efficient by allowing tasks to be run in parallel.

5. What do you understand about await in JavaScript?

The await keyword in JavaScript is used to pause the execution of a function until a Promise is resolved. This allows you to write asynchronous code that looks and feels like synchronous code.

6. Is it possible to use async/await with promise chains? If yes, how can this be achieved?

Yes, it is possible to use async/await with promise chains. In order to do this, you must first await the initial promise in the chain, and then you can continue working with the results of that promise as if they were synchronous. This can be a useful way to avoid callback hell when working with asynchronous code.

7. What’s the error handling strategy for promises that were rejected while awaiting?

There are a couple different ways to handle errors with async/await. The first is to use a try/catch block around your code. The second is to add a .catch() handler to your promise.

8. In which scenarios would you use synchronous code instead of asynchronous code?

There are a few reasons you might choose to use synchronous code instead of asynchronous code. One reason is if you are working with a very small amount of data that can be processed quickly. Another reason is if your code is already running in a synchronous environment and you don’t want to introduce the overhead of using asynchronous code. Finally, you might also choose to use synchronous code if you need to guarantee that certain operations will happen in a specific order.

9. Can you give me some examples of real world applications where async/await has been used?

Async/await has been used in a variety of real world applications, including:

-Web applications that need to load data from multiple sources simultaneously
-Chat applications that need to send and receive messages in real-time
-Games that need to perform complex calculations in the background while still providing a responsive experience to the user

10. Why are callback functions called after a certain amount of time even though the actual work was completed before the specified time interval?

This is due to the way that async/await functions work. When an async function is called, it will return immediately, even if the actual work is not yet finished. This is why callback functions are often used with async functions, so that the callback can be called when the work is actually finished.

Overview of the asynchronous model

The core of async programming is the Task and Task objects, which model asynchronous operations. They are supported by the async and await keywords. The model is fairly simple in most cases:

  • For I/O-bound code, you await an operation that returns a Task or Task inside of an async method.
  • For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

The await keyword is where the magic happens. It yields control to the caller of the method that performed await, and it ultimately allows a UI to be responsive or a service to be elastic. While there are ways to approach async code other than async and await, this article focuses on the language-level constructs.

History[edit]

F# added asynchronous workflows with await points in version 2.0 in 2007.[6] This influenced the async/await mechanism added to C#.[7]

Microsoft released a version of C# with async/await for the first time in the Async CTP (2011). And were later officially released in C# 5 (2012).[8]

Haskell lead developer Simon Marlow created the async package in 2012.[9]

Python added support for async/await with version 3.5 in 2015[10] adding 2 new keywords, async and await.

TypeScript added support for async/await with version 1.7 in 2015.[11]

Javascript added support for async/await in 2017 as part of ECMAScript 2017 JavaScript edition.

Rust added support for async/await with version 1.39.0 in 2019 [12] with 1 new keyword async and a lazy eval await pattern.[13]

C++ added support for async/await with version 20 in 2020 with 3 new keywords co_return, co_await, co_yield.

Swift added support for async/await with version 5.5 in 2021, adding 2 new keywords async and await. This was released alongside a concrete implementation of the Actor model with the actor keyword[14] which uses async/await to mediate access to each actor from outside.

The magic behind the C# await keyword

Now that we detailed the await keyword workflow we can measure how powerful it is. Some magic does occur under the hood to resume the execution once the task finishes. Let’s have a look at the thread stack trace after await taskA; in the main method.

123456789 ...ConsoleWriteLine(“Wait for taskA termination”);awaittaskA; Console.WriteLine(newSystem.Diagnostics.StackTrace()); ConsoleWriteLine($“The result of taskA is {taskA.Result}”);Console.ReadKey();}

Here it is:

The simple line await taskA; leads the C# compiler to generate a lot of code to pilot the runtime. Identifiers like AsyncState... and MoveNext() shows that a state machine is created for us to let the magic of code continuation happens seamlessly. Here is the assembly decompiled with ILSpy. We can see that a class is generated by the compiler for each usage of the await keyword:

Here is a call graph generated by NDepend of the methods of the Task Parallel Library (TPL) called by the generated code. To obtain such graph with methods and fields generated by the compiler, the following setting must be disabled first: NDepend > Project Properties > Analysis > Merge Code Generated by Compiler into Application Code

The details of what the C# compiler generates when it meets the keyword await is outside the scope of this article but you can deep dive in it in this Microsoft article. Just keep in mind that the code executed after an await keyword can eventually be executed by a random thread and that a lot of code that calls the TPL is generated to make this happen. Let’s explain how the random thread is chosen by he runtime.

So far we only demonstrated code executed in the context of a console application. The context in which some asynchronous code runs actually influences its workflow a lot. For example let’s run the same code in the context of a WPF application. Since it is convenient to keep the console output to show results of our experiments, let’s set the output type of our WPF assembly to Console Application, so a console is shown when the WPF app starts.

Now let’s execute the exact same code from within a WPF button click event handler:

12345678 publicpartial classMainWindow:Window{publicMainWindow(){InitializeComponent();}privateasyncvoidButton_Click(objectsender,RoutedEventArgse){ConsoleWriteLine($“Start Program”);Task<int>taskA =MethodAAsync();...

Here is the surprising result: the main thread is used to run everything! And task A loops are postponed after task B loops (except the first one).

This is totally different than what we had with our console application. The key is that in a WPF context (and also in a Winforms context) there is a synchronization context object, that can be obtained through SynchronizationContext.Current.

There is no synchronization context in a console application.

FAQ

Does C have async await?

In C. There’s no official support for await/async in the C language yet. Some coroutine libraries such as s_task simulate the keywords await/async with macros.

How does async work in C?

The idea is simple. The first time you call the async function, it will run like normal until it hits some form of await. Then it may return. Each time after that, the function jumps back to the await statement.

What is async await?

Await: Await function is used to wait for the promise. It could be used within the async block only. It makes the code wait until the promise returns a result. It only makes the async block wait.

What is C# await?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *