Asynchronous async/Await C#

Async/Await keyword

Visual Studio (.net framework 4.5) provides an asynchronous programming model. Compared with the previous implementation, the new asynchronous programming model reduces the complexity of use and is easier to maintain and debug. The compiler does a lot of complicated work for the user to achieve Asynchronous Programming Model[^4].

 

 

 

  1. Call the asynchronous method AccesstheWebAsync
  2. Create an instance of HttpClient and use HttpClient to get asynchronous data.
  3. Use Task to execute the method of obtaining data (assuming that it takes a long time to obtain data), without blocking the current thread , getStringTask represents the task in progress.
  4. Because getStringTask has not used the await keyword, it can continue to execute other tasks that do not depend on its return result, and execute DoIndependentWork synchronously.
  5. When the synchronization task DoIndependentWork is completed, it returns to the AccessTheWebAsync thread.
  6. Use await to force wait for getStringTask to complete and get the return value based on Task<String> type. (If getStringTask completes before the synchronization method DoIndependentWork is executed, the call will return to the AccessTheWebAsync thread, and calling await will perform an unnecessary suspend operation)
  7. When the web data is obtained, the return result is recorded in the Task and returned to the await caller (of course, the return value is not returned in the second line).
  8. Get data and return calculation results. 

 

Get to the bottom of things

 

The above is the official documentation. The example is clearly expressed in detail, but there is one problem that has not been solved (proved):

 

1. After the thread is returned to the thread pool at await, is the thread "really" consumed by other requests?

2. The server thread resources are certain. Who is actually executing the operation awaited by Await, or asynchronous IO operation?

3. If IO threads are used to perform asynchronous IO operations, what are the advantages compared to threads in thread pools? Or what is the advantage of asynchronous operation over synchronous operation?

 

Preconditions:

 

1. Compared with the Console application, you can use SetMaxThread of ThreadPool to simulate the maximum number of worker threads and IO threads supported by the current process.

2. The available number of worker threads and IO threads of the current process can be obtained through GetAvailableThreads of ThreadPool.

3. ThreadPool is process-based, each process has a thread pool, and the process of IIS Host can manage the thread pool separately.

4. If you want to simulate asynchronous IO thread operation files in a real sense, you need to set FileOptions.Asynchronous instead of just using methods such as BeginXXX. For details, please refer to [^1] Asynchronous IO thread.

5. When verifying synchronous and asynchronous calls, the number of tasks to be executed should be more than twice the current maximum worker thread, so that it can be detected that after Await releases the worker thread, other requests can continue to use the thread.

 

 

in conclusion:

 

1. Await uses asynchronous IO threads to perform tasks that operate asynchronously and release worker threads back to the thread pool.

2. The thread pool is divided into worker threads and asynchronous IO threads, which perform tasks at different levels.

3. The efficiency of using Await to perform asynchronous operations is not always higher than that of synchronous operations, and it needs to be judged according to the length of asynchronous execution.

4. When the worker thread and the IO thread switch to each other, there will be a certain performance consumption.

 


You can clone the code and review the code according to Commit, I believe you can understand the code intention, if not, please leave a message and I will improve :)

 

 

[GitHubRepo](https://github.com/Cuiyansong/Why-To-Use-Async-Await-In-DotNet.git)

 

 

copy code
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
 
namespace AsyncAwaitConsole
{
    class Program
    {
        static int maxWorkerThreads;
        static int maxAsyncIoThreadNum;
        const string UserDirectory = @"files\";
        const int BufferSize = 1024 * 4;
 
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) =>
            {
                Directory.Delete("files", true);
            };
 
            maxWorkerThreads = Environment.ProcessorCount;
            maxAsyncIoThreadNum = Environment.ProcessorCount;
            ThreadPool.SetMaxThreads(maxWorkerThreads, maxAsyncIoThreadNum);
 
            LogRunningTime(() =>
            {
                for (int i = 0; i < Environment.ProcessorCount * 2; i++)
                {
                   Task.Factory.StartNew(SyncJob, new {Id = i});
                }
            });
 
            Console.WriteLine("===========================================");
 
            LogRunningTime(() =>
            {
                for (int i = 0; i < Environment.ProcessorCount * 2; i++)
                {
                    Task.Factory.StartNew(AsyncJob, new { Id = i });
                }
            });
 
            Console.ReadKey();
        }
 
        static void SyncJob(dynamic stateInfo)
        {
            var id = (long)stateInfo.Id;
            Console.WriteLine("Job Id: {0}, sync starting...", id);
 
            using (FileStream sourceReader = new FileStream(UserDirectory + "BigFile.txt", FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize))
            {
                using (FileStream destinationWriter = new FileStream(UserDirectory + $"CopiedFile-{id}.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, BufferSize))
                {
                    CopyFileSync(sourceReader, destinationWriter);
                }
            }
            Console.WriteLine("Job Id: {0}, completed...", id);
        }
 
        static async Task AsyncJob(dynamic stateInfo)
        {
            var id = (long)stateInfo.Id;
            Console.WriteLine("Job Id: {0}, async starting...", id);
 
            using (FileStream sourceReader = new FileStream(UserDirectory + "BigFile.txt", FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.Asynchronous))
            {
                using (FileStream destinationWriter = new FileStream(UserDirectory + $"CopiedFile-{id}.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, BufferSize, FileOptions.Asynchronous))
                {
                    await CopyFilesAsync(sourceReader, destinationWriter);
                }
            }
            Console.WriteLine("Job Id: {0}, async completed...", id);
        }
 
        static async Task CopyFilesAsync(FileStream source, FileStream destination)
        {
            var buffer = new byte[BufferSize + 1];
            int numRead;
            while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0)
            {
                await destination.WriteAsync(buffer, 0, numRead);
            }
        }
 
        static void CopyFileSync(FileStream source, FileStream destination)
        {
            var buffer = new byte[BufferSize + 1];
            int numRead;
            while ((numRead = source.Read(buffer, 0, buffer.Length)) != 0)
            {
                destination.Write(buffer, 0, numRead);
            }
        }
 
        static void LogRunningTime(Action callback)
        {
            var awailableWorkingThreadCount = 0;
            var awailableAsyncIoThreadCount = 0;
 
            var watch = Stopwatch.StartNew();
            watch.Start();
 
            callback();
 
            while (awailableWorkingThreadCount != maxWorkerThreads)
            {
                Thread.Sleep(500);
                ThreadPool.GetAvailableThreads(out awailableWorkingThreadCount, out awailableAsyncIoThreadCount);
 
                Console.WriteLine("[Alive] working thread: {0}, async IO thread: {1}", awailableWorkingThreadCount, awailableAsyncIoThreadCount);
            }
 
            watch.Stop();
            Console.WriteLine("[Finsih] current awailible working thread is {0} and used {1}ms", awailableWorkingThreadCount, watch.ElapsedMilliseconds);
        }
    }
}
copy code
View Code

 

 

Note: Async/Await does not create a new thread, but a thread based on the current synchronization thread, which is more convenient to use than Thread/Task or thread-based BackgroundWorker. The function of the Async keyword is to identify the need to wait for the completion of the method execution at Await. Too much await will not cause a compiler error, but if there is no await, the method will be converted to a synchronous method. 

 

IIS Host based application 

 

 

 

 1.  IIS can host ThreadPool by adding it in IIS Application Pool, and you can set the number of Working Thread and Async IO Thread.

2. The server accepts the request and obtains the currently idle thread from the thread pool for processing. If the request is processed synchronously, the current thread waits for the processing to complete and then returns to the thread pool. The number of server threads is limited, and when the number of server threads exceeds the maximum request that IIS can handle , a 503 error will be returned.

3. When the server accepts the request and processes the request asynchronously, when it encounters an asynchronous IO type operation, the current thread returns to the thread pool. When the asynchronous operation is completed, get a new thread from the thread pool and continue to execute the task until the subsequent task is completed [^7].

 

For example, adding the awaitable method to the MVC Controller proves that when a blocking task is encountered, the current thread returns to the thread pool immediately. When the blocking task completes, a new thread will be obtained from the thread pool to perform subsequent tasks:

 

     var availableWorkingThreadCount = 0;

                var availableAsyncIoThreadCount = 0;

                ThreadPool.GetAvailableThreads(out availableWorkingThreadCount, out availableAsyncIoThreadCount);

                AddErrors(new IdentityResult(string.Format("[IIS Host] Thread Id {0}, ThreadPool Thread: {1}",

                    Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)));

                AddErrors(new IdentityResult(string.Format("[IIS Host] current working thread: {0}, current async thread: {1}", availableWorkingThreadCount, availableAsyncIoThreadCount)));

 

                HttpClient httpClient = new HttpClient();

                var response = httpClient.GetStringAsync("https://msdn.microsoft.com/en-us/library/system.threading.thread.isthreadpoolthread(v=vs.110).aspx");

                await response;

 

                AddErrors(new IdentityResult(string.Format("[IIS Host] Thread Id {0}, ThreadPool Thread: {1}",

                Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)));

 

 

[IIS Host] Thread Id 4, ThreadPool Thread: True

[IIS Host] current working thread: 4094, current async thread: 1000

[IIS Host] Thread Id 9, ThreadPool Thread: True

 

 

in conclusion:

  • Synchronous method application scenarios:
    • Request processing is very fast
    • Code simplicity is greater than code efficiency
    • Mainly based on CPU time-consuming operations

 

  • Asynchronous method application scenarios:
    • Based on Network or I/O type operations, not CPU time-consuming operations
    • Enables IIS to handle more requests by using asynchronous methods when blocking operations are the bottleneck
    • Parallelization is more important than code simplicity
    • Provide a mechanism for users to cancel long-running requests 

 

 More thread optimizations

Stephen Cleary introduced the specification of three asynchronous programming models[^5]:

1. Avoid Async Void, void and task<T> will generate different exception types

2. Always use the Async keyword

3. Use Task.WaitXXX instead of Task.WhenXXX

4. Configure context  try not to capture thread context, use Task.ConfigureAwait(false)

 

quote

[^1] "CLR via C# Edition3" Chapter 25 Thread Basics

[^2] Encyclopedia - Bee Dance: http://baike.baidu.com/link?url=ixwDjgocRIg4MJGTQyR3mUC1fspHZtfPYEtADfJAJdC6X0xIVU4lJUe2iVvCNHEj3JeE1JalBCNyyPcVMdhaoyBFz_xXcLPMEJ_2iUcHjithF8_F8A9yI61EAzpmpYR

[^3] Asynchronous Programming Model: https://msdn.microsoft.com/en-us/library/mt674882.aspx

[^4] C# Async, Await keywords: https://msdn.microsoft.com/library/hh191443(vs.110).aspx

[^5] Task Best Practice[Stephen Cleary]: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

[^6] Chinese translation version of the best practice of asynchronous programming model: http://www.cnblogs.com/farb/p/4842920.html

[^7] Synchronous vs Asynchronous Controller: https://msdn.microsoft.com/en-us/library/ee728598%28v=vs.100%29.aspx

[^8] IIS Optimization: https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4 \

source

Author: Stephen Cui
Source: http://www.cnblogs.com/cuiyansong

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325251260&siteId=291194637
Recommended