(16) Instance understanding of threads: Await, Async, ConfigureAwait


    Continuing with the example of (15)

1. The role of ConfigureAwait()

        private async void BtnAsync_Click(object sender, EventArgs e)//异步
        {
            Stopwatch sw = Stopwatch.StartNew();
            TxtInfo.Clear();
            AppendLine("异步检索开始...");
            AppendLine($"当前线程Id:{Environment.CurrentManagedThreadId}");//b
            int idx = 0;
            foreach (var b in Data.Books)
            {
                string t = await Task.Run(b.Search).ConfigureAwait(false);//a
                AppendLineThread($"{++idx}.{t}--线程Id:{Environment.CurrentManagedThreadId}");//c
            }
            sw.Stop();
            AppendLineThread($"异步检索完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");
        }    


    
    1. What does the ConfigureAwait(false) added after a above mean?
        ConfigureAwait(true) and ConfigureAwait(false) are also used to configure async/await operations. They are used to control whether asynchronous operations continue to execute in the original context after await.
        When ConfigureAwait(true), the asynchronous operation will return to the original context (usually the caller thread or UI thread) after await to continue execution.
        When ConfigureAwait(false), the asynchronous operation will continue to execute in the non-original context (generally the current asynchronous thread) after await.
        
        Understanding Example (2):
        Suppose you are the manager of a restaurant and you need to arrange for waiters to perform some tasks. The waiter is your thread, and the task is an asynchronous operation. You can choose from two different ways to schedule your waiters to perform tasks.
        When you use ConfigureAwait(true), it's like you let the waiter perform the task in the original context. This means that the waiter will continue to perform tasks where you are. For example, if you are greeting customers at the front desk and then encounter an asynchronous task (such as answering a phone call), you can choose to have the waiter continue the task beside you, so that he can continue handling the customer immediately after you answer the phone.
        And when you use ConfigureAwait(false), it's like you let the waiter leave the original context to perform tasks. This means that the waiter will leave your place to perform the task. For example, if you are greeting customers at the front desk and then encounter an asynchronous task (such as processing a payment), you can choose to have the waiter "leave your seat" to process the payment so that you can continue to serve other customers.
        So, ConfigureAwait(true) allows the asynchronous operation to continue executing in the original context, just like letting the waiter continue to perform tasks beside you. ConfigureAwait(false) allows the asynchronous operation to continue executing in a non-original context, just like letting the waiter leave your location to perform the task.


    2. What are the benefits of true and false?
        Benefits of ConfigureAwait(true):
        Preserve the current context: In some cases, you may need to return to the original context after the asynchronous operation is completed. For example, you call an asynchronous operation on the UI thread and then The UI needs to be updated after the operation is completed. Using ConfigureAwait(true) can ensure that asynchronous operations continue to execute in the original context. Due to thread switching during asynchronous operations, there is the overhead of context recovery.
        Simplify the code: If you are sure that the asynchronous operation will not cause thread context-related problems and want to keep executing in the original context, then using ConfigureAwait(true) can simplify the code and avoid the need to explicitly specify ConfigureAwait(false).
        
        Benefits of ConfigureAwait(false):
        Improved performance: If your asynchronous operation does not need to return to the original context and has no dependencies on the UI or a specific context, using ConfigureAwait(false) can avoid unnecessary thread switching during asynchronous operations. and context recovery overhead, thereby improving performance.
        Avoid deadlocks: In some cases, when asynchronous operations depend on a specific context, using ConfigureAwait(false) can avoid the possibility of deadlocks. For example, using ConfigureAwait(true) on the UI thread can cause an asynchronous operation to deadlock while waiting for UI thread resources because the UI thread is waiting for the asynchronous operation to complete.
        Generally speaking, ConfigureAwait(true) is suitable for situations where the original context needs to be retained, which can avoid the overhead of thread switching and context recovery, and simplify the code. ConfigureAwait(false) is suitable for situations where there is no need to return to the original context, which can improve performance and avoid deadlocks.
        Note that using ConfigureAwait(false) also means that you ensure that no resources or data related to the UI thread context are used in the asynchronous operation. Otherwise, thread safety issues or other bugs may result.

    
    3. Can the UI thread and the asynchronous thread be the same thread?
        UI threads are not absolutely different from asynchronous threads. They are similar to objects and can point to the same thread at the same time. For example, the UI thread can point to the UI thread itself, and the asynchronous thread can also point to the UI thread at the same time.
        Therefore, the UI thread and the asynchronous thread can point to the same actual thread at the same time.
        UI threads and asynchronous threads are actually the roles or identities (variable names) of threads that are used to distinguish their different tasks and behaviors in the application. While they can point to the same thread in some cases, they are often used for different purposes and contexts.

        The UI thread is usually responsible for rendering the user interface, responding to user input, and handling UI events. Asynchronous threads are generally used to perform time-consuming operations to avoid blocking the UI thread, as well as to perform tasks in the background or handle concurrent operations.
        Although it is possible for the UI thread and the asynchronous thread to point to the same thread, context switching and thread safety between threads still need to be considered. The UI thread and asynchronous thread perform task processing in the corresponding context to ensure correct execution and interaction.

        In summary, the UI thread and the asynchronous thread can share the same thread object, but they have different roles and tasks in the application.
    
    
    4. The effects of true and false.
        The task in the above example has nothing to do with the context, so using true or false will not have much impact. But we can check the changes in the thread ID:
        
        
        
        the left side is true. After the asynchronous thread y operates task.run at a, according to the setting of true, control will return the thread y to the thread pool (let the thread pool manage y, which is released Or use it, it has nothing to do with it now), then, the control switch is restored to the original context (that is, the UI thread). At this time, the UI thread is executing to ensure that subsequent code is executed on the UI thread. Therefore, the UI thread is executing at b, and the UI thread is also executing at c (the UI thread entrusts the UI to do things by itself). Therefore, the thread ID at c is the same as the thread ID of the UI thread. The above IDs are all 1.
        Note:
        In some programming frameworks and operating systems, the ID of the UI thread may be pre-allocated to 1. Note that this result is a performance under a specific environment and does not apply to all programming frameworks and operating systems. In other environments, the UI thread's ID may have different rules or ways of assigning it. Therefore, when writing code, it is best to avoid relying on the specific environment's way of assigning thread IDs, and instead use the provided API or methods to obtain the thread ID.
        
        The right side is false. After the asynchronous thread y operates task.run at a, according to the setting of false, thread y will not be returned to the thread pool, nor will it try to restore the original context (such as switching to the UI thread), and the control operation rights are still Hold it tightly in thread y, and then thread y takes charge and continues to execute the code below b. This asynchronous thread y is intelligently allocated by the thread pool during task.run, so each task.run corresponds to an asynchronous thread. c is also executed by this asynchronous thread, so the asynchronous thread ID displayed at c due to the allocation of the thread pool is random and may be the same or different. So the ID at b is 1, and at c it is randomly determined by the thread pool.
        When it is false, if you operate UI controls after c, such as TxtInfo.AppendText="1111";, an error will occur. Because after false, the returned thread can only handle things unrelated to the UI. As a result, now processing TxtInfo will cause an exception. The above code works fine because everything behind it is delegated to AppendLintThread.

2. Await/Async


    1. Example interface
        
        


        
        


        
        Code:

        public Form1()
        {
            InitializeComponent();
        }

        private readonly StringBuilder strResult = new StringBuilder();

        private void Test_ConfigureAwait(object sender, EventArgs e)
        {
            Stopwatch sw = Stopwatch.StartNew();
            string s1 = cbAwait.Checked.ToString();
            string s2 = cbConfigureAwait.Checked.ToString();
            strResult.Clear();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始:Await:{s1},ConfigureAwait:{s2}");
            ChildMethod();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始等待");
            Thread.Sleep(3000);
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程结束{sw.ElapsedMilliseconds}ms");
            MessageBox.Show(Owner, "主线程结束,输出结果", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            Print(strResult.ToString());
        }

        private async void ChildMethod()
        {
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod开始......");
            Stopwatch sw = Stopwatch.StartNew();
            if (cbAwait.Checked)
            {
                await Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            else
            {
                Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod结束{sw.ElapsedMilliseconds}ms");
        }

        private void Print(string s)
        {
            txtInfo.AppendText(s + $"{Environment.NewLine}");
            txtInfo.ScrollToCaret(); txtInfo.Refresh();
        }

        private void BtnPrint_Chick(object sender, EventArgs e)
        { Print(strResult.ToString()); }


        
    
    2. In C#, use the `await` keyword to achieve asynchronous execution, and do not block the current thread when waiting for the asynchronous result to return, but return control to the caller. Typically, this caller can be the UI thread, but it can also be other threads. When the caller encounters the `await` keyword, it pauses execution and allows other code to continue executing without blocking the thread.

        When the `await` keyword is reached, the asynchronous operation will start executing, and the caller will continue to execute the code following the keyword. When the asynchronous operation completes and returns a result, the caller resumes execution and can process the results of the asynchronous operation. Whether the caller ends before or after `await`, it will not affect the execution of the asynchronous operation.

        It should be noted that when using `await`, the caller must be in some kind of asynchronous context, such as using an asynchronous method, an asynchronous event handler or creating an asynchronous operation through a method such as `Task.Run`. This allows asynchronous operations to be managed and scheduled correctly and resume execution at the appropriate time.

        To summarize, the `await` keyword allows code to not block while waiting for an asynchronous result to return, and returns control to the caller so that it can continue executing other code. The caller can be the UI thread or other threads, and the order of execution will depend on when the asynchronous operations complete.
    
    
    3. When nothing is selected above,
        the main method first calls the sub-method. Since Task.Run is asynchronous, the sub-method flashes and directly executes the end information of the bottom sub-method. As for task.run, let it wait for 2 seconds. Add information, and during this period, the main method ignores the sub-method as soon as it is called, and executes directly to the delay of 3 seconds, so when the sub-method delays for 2 seconds, the main method's delay of 3 seconds will also expire later. , and then add the information about the end of the main method.
        
        When only Await is selected,
        after the main method calls the sub-method, it will continue to execute itself. When a submethod encounters await Task.run, it needs to block execution and wait for 2 seconds. Because configureawait is not selected and is false, the code after task.run will still continue to be executed directly by the asynchronous thread until the information of the submethod is appended. Of course, The information must be appended earlier than the 3 seconds delay of the main method, so the final information displayed is the end of the main method.
    
    
    
    3. Take another look at the effect of ConfigureAwait
        
        


        
        
        


        
        
        When cbAwait and cbConfigureAwait are selected.
        The main method calls the sub-method and enters await task.run to use an asynchronous thread to perform asynchronous operations. When it is completed, because configureawait is true, that is, this asynchronous thread y must hand over power and needs to switch to the caller or UI thread, that is, the main method The thread goes up. Here, the main method thread UI thread is delaying for 3 seconds. If there is no idle time, thread y will have to wait for it to be idle until it gets the space of the UI thread during messagebox.show, so the asynchronous thread y returns to the thread pool. , and when the UI thread that is popping up the information box gets space, it returns to the sub-method and continues to execute downwards. After the execution of the last information of the sub-method is completed, it returns to the main method and continues downward, that is, prints the information. print it out. So the information you see is that the main method information is completed, and the last is the sub-method information.
        
        
        Question: Why does it say that messagebox.show is free?
        Answer: In order to observe when the information is added, we make the following modifications:

        private void Test_ConfigureAwait(object sender, EventArgs e)
        {
            Stopwatch sw = Stopwatch.StartNew();
            string s1 = cbAwait.Checked.ToString();
            string s2 = cbConfigureAwait.Checked.ToString();
            strResult.Clear();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始:Await:{s1},ConfigureAwait:{s2}");
            ChildMethod();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程开始等待");
            Thread.Sleep(3000);
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】主线程结束{sw.ElapsedMilliseconds}ms");
            strResult.AppendLine($"对话框前:{DateTime.Now.TimeOfDay}");
            MessageBox.Show(Owner, "主线程结束,输出结果", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            strResult.AppendLine($"对话框后:{DateTime.Now.TimeOfDay}");
            Print(strResult.ToString());
        }

        private async void ChildMethod()
        {
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod开始......");
            Stopwatch sw = Stopwatch.StartNew();
            if (cbAwait.Checked)
            {
                await Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }
            else
            {
                Task.Run(() =>
                {
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程开始......");
                    Thread.Sleep(2000);
                    strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】子线程延时2000ms结束");
                }).ConfigureAwait(cbConfigureAwait.Checked);
            }

            strResult.AppendLine($"子方法延时前{DateTime.Now.TimeOfDay}");
            Thread.Sleep(3000);
            strResult.AppendLine($"子方法延时后{DateTime.Now.TimeOfDay}");
            sw.Stop();
            strResult.AppendLine($"【{Environment.CurrentManagedThreadId}】ChildMethod结束{sw.ElapsedMilliseconds}ms");
        }


    
        The result is:
        


        
        You can see that the asynchronous thread is idle in the information box, thus completing the switch to the UI. In the UI, it goes to the sub-method to continue completing the remaining code, which includes a deliberately added 3-second delay. It also gets Execute until all sub-methods are completed before returning to the main method to print.
        
    
    
    

Guess you like

Origin blog.csdn.net/dzweather/article/details/132656741