myesn

myEsn2E9

hi
github

C#: Task Asynchronous Programming Model

Using asynchronous programming can avoid performance bottlenecks and improve the overall responsiveness of applications. However, traditional techniques for writing asynchronous applications can be quite complex, making them difficult to write, debug, and maintain.

C# supports a simplified approach, namely asynchronous programming, which leverages the asynchronous support of the .NET runtime. The compiler takes care of the heavy lifting that developers had to do in the past, while your application retains a logical structure similar to synchronous code. Thus, you only need to do a small amount of work to gain all the benefits of asynchronous programming.

This article outlines when and how to use asynchronous programming and includes links to detailed information and examples.

Asynchronous Enhances Responsiveness#

Asynchronous is crucial for activities that may block, such as network access. Accessing network resources can sometimes be slow or delayed. If such activities are blocked in a synchronous process, the entire application must wait. However, in an asynchronous process, the application can continue with other work that does not depend on network resources until the potentially blocking task is completed.

The table below shows typical areas where asynchronous programming improves responsiveness. APIs from .NET and Windows Runtime that include methods supporting asynchronous programming are listed.
image

Asynchronous is particularly important for applications accessing the UI thread, as all UI-related activities typically share a single thread. If any process in a synchronous application is blocked, then all processes will be blocked. Your application stops responding, and you may think it has failed, when in fact it is just waiting.

When you use asynchronous methods, the application will continue to respond to UI operations. For example, you can resize or minimize the window, or you can close the application if you do not want to wait for it to complete.

When designing asynchronous operations, the asynchronous approach adds options equivalent to automatic transfer to the list of options. That is, developers need to invest less effort to gain all the advantages of traditional asynchronous programming.

Asynchronous Methods Are Easy to Write#

Using the async and await keywords in C# is central to asynchronous programming. By using these two keywords, you can create asynchronous methods using resources in the .NET Framework, .NET Core, or Windows Runtime as easily as creating synchronous methods. Asynchronous methods defined with the async keyword are called async methods.
The following example demonstrates an async method.
You can download the complete Windows Presentation Foundation (WPF) example from Asynchronous programming with async and await in C#.

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

You can learn several practical methods from the previous example. The first is the method signature, which includes the async modifier. The return type is Task<int> (for more options, see the "Return Types" section). The method name ends with Async. In the method body, GetStringAsync returns a Task<string>. This means that when you await the task to complete, you will get a string (the contents). You can perform work that does not depend on the string returned by GetStringAsync before awaiting the Task.

Pay special attention to the await operator:

  • GetUrlContentLengthAsync cannot continue executing until getStringTask completes.
  • At the same time, control returns to the caller of GetUrlContentLengthAsync.
  • When getStringTask completes, control resumes here.
  • Then, the await operator retrieves the string result from getStringTask.

The return statement specifies an integer result. Any method waiting for GetUrlContentLengthAsync will receive the length value.

If GetUrlContentLengthAsync has no other work to do between calling GetStringAsync and awaiting its completion, you can simplify the code with the following single statement:

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

The following features summarize the asynchronous method in the previous example:

  • The method signature includes the async modifier.
  • The names of asynchronous methods typically end with "Async".
  • The return type can be one of the following types:
    • If the method has a return statement and the operand type is TResult, return Task.

    • If the method has no return statement or the return statement has no operand, return Task.

    • If writing asynchronous event handlers, return void.

    • Any other type that has a GetAwaiter method.

      For more information, see the Return types and parameters section.

  • The method typically contains at least one await expression, which marks a point where the method cannot continue until the awaited asynchronous operation completes. Meanwhile, the method is suspended, and control is returned to the caller. The next section of this article will explain what happens at the suspension point.

In asynchronous methods, you use the provided keywords and types to indicate what to do, while the compiler takes care of the rest, including tracking what must happen when control returns to the suspended await point. Some common flows (like loops and exception handling) may be difficult to manage in traditional asynchronous code. But in asynchronous methods, you can write these elements as you would in synchronous solutions, and the issues are resolved.

To learn more about asynchrony in the older .NET Framework, see TPL and traditional .NET Framework asynchronous programming.

What Happens in an Async Method#

The most important aspect of asynchronous programming is understanding how control flow moves from one method to another. The following diagram guides you through this process:

When the caller invokes the asynchronous method, the numbers in the diagram correspond to the following steps:

  1. The caller invokes and awaits the GetUrlContentLengthAsync asynchronous method.

  2. GetUrlContentLengthAsync creates an HttpClient instance and calls the GetStringAsync asynchronous method to download the website content as a string.

  3. Some progress is suspended in GetStringAsync. It may need to wait for the website to download or for other blocking activities. To avoid blocking resources, GetStringAsync yields control back to its caller GetUrlContentLengthAsync.
    GetStringAsync returns a Task, where TResult is a string, and GetUrlContentLengthAsync assigns the task to the getStringTask variable. This task represents that the call to GetStringAsync is in progress, promising to produce the actual string value when the work is completed.

  4. Since GetUrlContentLengthAsync has not yet awaited getStringTask, it can continue with other work that does not depend on the final result of GetStringAsync. This work (continuing with other work) is represented by calling the synchronous method DoIndependentWork.

  5. DoIndependentWork is a synchronous method that returns to the caller after completing its work.

  6. GetUrlContentLengthAsync has no more work to complete before the result of getStringTask is available. Next, GetUrlContentLengthAsync wants to calculate and return the length of the downloaded string, but it cannot calculate that value until it has the string.
    Thus, GetUrlContentLengthAsync uses the await operator to suspend progress and yield control back to the method that called GetUrlContentLengthAsync. GetUrlContentLengthAsync returns a Task<int> to the caller. This task represents a promise to produce an integer result, which is the length of the downloaded string.

    Inside the caller, the processing pattern continues. Before awaiting the result of GetUrlContentLengthAsync, the caller may perform other work that does not depend on the result of GetUrlContentLengthAsync, or the caller may choose to await immediately. The caller is waiting for GetUrlContentLengthAsync, while GetUrlContentLengthAsync is waiting for GetStringAsync.

  7. GetStringAsync completes and produces a string result. The returned string result is not what you might expect. (Remember, in step 3, that method has already returned a Task). Instead, the string result is stored in the task getStringTask, which represents the completion of the method. The await operator retrieves the result from getStringTask. The assignment statement assigns the retrieved result to the contents variable.

  8. When GetUrlContentLengthAsync obtains the string result, it can calculate the string length. Then, GetUrlContentLengthAsync also completes its work, and the awaiting event handler can resume execution.
    In the complete example provided at the end of this article, you can confirm that the event handler retrieves and prints the length value. If you are just starting with asynchronous programming, take a moment to consider the difference between synchronous and asynchronous behavior. Synchronous methods return when their work is complete (step 5), but asynchronous methods return a task value when their work is suspended (steps 3 and 6). When an asynchronous method finally completes its work, the task is marked as complete, and the result (if any) is stored in the task.

API Async Methods#

.NET Framework 4.5+ and .NET Core include many methods that support asynchronous programming, identifiable by the member name suffix "Async" and a return type of Task or Task. For example, the System.IO.Stream class has methods like CopyToAsync, ReadAsync, and WriteAsync, as well as synchronous methods CopyTo, Read, and Write.

Windows Runtime also includes many methods that can use async and await in Windows applications. For more information, see Threading and Asynchronous Programming for UWP Development and Asynchronous Programming (Windows Store Apps) and Quickstart: Calling Asynchronous APIs in C# or Visual Basic (if you are using an earlier version of Windows Runtime).

Threads#

Asynchronous methods are designed for non-blocking operations. While the awaited task is running, the **await expression does not block the current thread**. Instead, the expression marks the rest of the method to continue and returns control to the caller of the async method.

**async and await keywords do not create additional threads. Asynchronous methods do not require multithreading because they do not run on their own thread**. The method runs in the current synchronization context and only uses time on the thread while the method is active. You can use Task.Run to move CPU-bound work to a background thread, but background threads do not help with processes waiting for result availability.

Compared to existing approaches, the async-based asynchronous programming model is preferable in almost all cases. Especially for I/O-bound operations, asynchronous methods are superior to the BackgroundWorker class because the code is simpler and does not require preventing race conditions. When combined with the Task.Run method, asynchronous programming also outperforms the BackgroundWorker class for CPU-bound operations, as asynchronous programming separates the coordination details of running code from Task.Run to the thread pool.

Async and Await#

When a method is specified as an asynchronous method using the async modifier, the following two features are enabled:

  • The marked async method can use await to specify a suspension point. The await operator tells the compiler that the asynchronous method cannot continue executing after that suspension point until the awaited asynchronous process completes. During this time, control returns to the caller of the asynchronous method.

    An asynchronous method suspended at an await expression does not cause the method to exit, and the finally block will not run.

  • The marked async method itself can be awaited by the method that calls it.

An async method typically contains one or more await operators, but the compiler will not throw an error if there are no await expressions (it will just issue a warning). If an async method does not use the await operator to mark a suspension point, the method will execute like a synchronous method, despite the async modifier. The compiler will issue a warning for such methods, strongly advising against this practice, as such code is very inefficient.

async and await are contextual keywords. For detailed information and examples, see the following topics:

Return Types and Parameters#

In asynchronous methods, you typically return a Task or Task. Inside asynchronous methods, the await operator serves as the Task returned from another asynchronous method call.

If the method contains a return statement specifying an operand of type TResult, then Task is specified as the return type.

If the method has no return statement or contains a return statement without an operand, use Task as the return type.

You can also specify any other return type as long as that type has a GetAwaiter method. ValueTask is one such type, provided in the System.Threading.Tasks.Extension NuGet package.

The following example shows how to declare and call methods that return Task or Task:

async Task<int> GetTaskOfTResultAsync()
{
    int hours = 0;
    await Task.Delay(0);

    return hours;
}

Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()
{
    await Task.Delay(0);
    // No return statement needed
}

Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();

Each returned Task represents ongoing work. The Task encapsulates information about the state of the asynchronous process, which may ultimately be the final result from that process or an exception raised by that process (if unsuccessful).

Asynchronous methods can also have a return type of void. This return type is primarily used to define event handlers that require a void return type. Asynchronous event handlers typically serve as the entry point for asynchronous programs.

Asynchronous methods with a return type of void cannot be awaited, and the caller cannot catch any exceptions thrown by that method.

Asynchronous methods cannot declare in, ref, or out parameters, but can call methods with such parameters. Similarly, asynchronous methods cannot return values by reference, but can call methods that have ref return values.

For more information and examples, see Async return types (C#).

Asynchronous APIs in Windows Runtime programming have one of the following return types (similar to Task):

Naming Convention#

By convention, methods that return common awaitable types (such as Task, Task<T>, ValueTask, ValueTask<T>) should end with "Async". Methods that start asynchronous operations but do not return awaitable types should not be named with "Async" at the end, but can start with "Begin", "Start", or other verbs to indicate that this method will not return or throw the operation result.

If an event, base class, or interface contract suggests a different name, you can ignore that convention (ending with "Async"). For example, you should not rename common event handlers like OnButtonClick.

image

See also#

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.