(17)线程的实例认识:wait,waitany,waitall,及经典死锁问题

news/2024/4/15 14:14:20

   
一、文件构成

    1、界面:一个textbox,四个button。
        

        
    2、程序:前面(15)的book类与data类

        private void AppendLine(string s){txtInfo.AppendText(string.IsNullOrEmpty(txtInfo.Text) ? s : $"{Environment.NewLine}{s}");txtInfo.ScrollToCaret();txtInfo.Refresh();}private void InvworkAppendLine(string text){ BeginInvoke(new Action(() => AppendLine(text))); }private void BtnWait_Click(object sender, EventArgs e)//单一任务同步等待完成{Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("Wait开始...");for (int i = 0; i < Data.Books.Count; i++){Task<string> t = Task.Run(() =>{return Data.Books[i].Search();});//Task.wait()方法,调用线程阻塞在wait处,出现两种情况结束等待://1.线程执行完毕;//2.任务本身已经取消或引发异常t.Wait();//当前调用线程上同步阻塞等待AppendLine($"{i}.{t.Result}");}sw.Stop();AppendLine($"Wait完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}private void BtnWaitAll_Click(object sender, EventArgs e)//同步等待全部完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAll开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() => { return book.Search(); }));}AppendLine($"等待前{DateTime.Now.TimeOfDay}");Task.WaitAll(ts.ToArray());AppendLine($"等待前{DateTime.Now.TimeOfDay}");foreach (var t in ts){ AppendLine($"{t.Result}.{t.Id}.{t.Status}"); }sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}private void BtnWaitAny_Click(object sender, EventArgs e)//同步等待任一完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAny开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() =>{return book.Search();}));}Task.WaitAny(ts.ToArray());//ts任务集合中任一任务完成,调用线程就继续向后执行。foreach (var t in ts){AppendLine($"{t.Id}.{t.Status}");}sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}private void BtnDeadLock_Click(object sender, EventArgs e)//死锁{Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("Wait开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];var idx = i + 1;var task = book.SearchAsync();task.Wait();AppendLine($"{idx}.{task.Result}");}sw.Stop();AppendLine($"Wait完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


  


二、Wait

        private void BtnWait_Click(object sender, EventArgs e)//单一任务同步等待完成{Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("Wait开始...");for (int i = 0; i < Data.Books.Count; i++){Task<string> t = Task.Run(() =>{return Data.Books[i].Search();});//Task.wait()方法,调用线程阻塞在wait处,出现两种情况结束等待://1.线程执行完毕;//2.任务本身已经取消或引发异常t.Wait();//a 当前调用线程上同步阻塞等待AppendLine($"{i}.{t.Result}");//b}sw.Stop();AppendLine($"Wait完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


    
    1、t.Wait();
        Task.Wait方法用于阻塞当前线程,并等待异步操作的完成。它是一个实例方法,针对具体的单一任务使用,用法:
            task.Wait(); // 阻塞当前线程,直到任务完成
        当调用Task.Wait方法时,当前线程将被阻塞,直到对应的任务(task)完成。如果任务已经完成,Wait方法会立即返回。如果任务尚未完成,Wait方法会一直阻塞当前线程直到任务完成。

        注意:task.Wait方法可能会导致线程阻塞,可能影响程序的响应性。因此,在使用Wait方法时,需要权衡其对程序性能和响应性的影响。

        另外,从.NET Framework 4.5开始,推荐使用await关键字结合异步方法进行异步操作的等待,而不是直接使用Task.Wait方法。使用await可以让异步代码在等待异步操作完成时释放当前线程并进行其他工作,从而提高程序的性能和响应性。
    
        扩展:它有几个重载,可以设置取消或运行时间,方便进行控制。
    
    2、t.Result
        task.Result 是一个同步方法,它会阻塞当前线程,直到任务完成并返回结果。如果任务已经完成,它会立即返回结果;如果任务尚未完成,它会阻塞当前线程直到任务完成。这意味着如果在主线程中使用 task.Result,它会阻塞主线程,导致应用程序无响应,看似"死了"。

        而使用 await task 或 task.Wait() 方法时,当前线程会被暂停并释放,允许其他代码在此期间执行。这被称为异步等待。一旦任务完成,当前线程会重新获得控制。

        总结:task.Result 是同步等待任务结果,会阻塞当前线程;而使用 await task 或 task.Wait() 是异步等待任务完成,允许其他代码执行。
        
        因此上面的a处t.Wait是可以省略的,因为它也是同步阻塞等待,省略后,t.Result的结果如果没出来也会同步阻塞等待,直到结果返回。所以上面的结果是逐个出来,前面一个任务完成后才开始循环中的第二个任务。
      


        
    
三、WaitAll

        private void BtnWaitAll_Click(object sender, EventArgs e)//同步等待全部完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAll开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() => { return book.Search(); }));}AppendLine($"等待前{DateTime.Now.TimeOfDay}");Task.WaitAll(ts.ToArray());AppendLine($"等待前{DateTime.Now.TimeOfDay}");foreach (var t in ts){ AppendLine($"{t.Result}.{t.Id}.{t.Status}"); }sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


    
    1、Task.WaitAll 方法是一个静态方法,用于等待多个任务完成。它接受一个 Task 数组作为参数,并阻塞当前线程,直到所有任务都完成。
        使用 Task.WaitAll 方法可以方便地等待多个任务并发地执行,而不需要手动跟踪每个任务的状态。它可以在需要等待多个任务完成后再继续执行的场景中非常有用,例如在并行处理数据或者执行批量任务时。
        使用 Task.WaitAll 方法等待所有任务完成。一旦所有任务都完成,就会继续执行后续的代码。
        注意,在等待多个任务时,如果其中一个任务发生异常,Task.WaitAll 方法会将所有任务的异常都聚合到一个 AggregateException 对象中,并将其抛出。因此,在使用 Task.WaitAll 方法时,最好使用 try-catch 块来处理可能的异常。
        总结:Task.WaitAll 方法用于等待多个任务完成,它阻塞当前线程直到所有任务都完成。它适用于需要等待多个任务并发执行的场景。
        
        警告:WaitAll()如果不带参数,将不会等待任何任务,起不到等待所有任务的作用。
            常见参数是params Task[]数组形式,也可以设置超时等待和可取消。
    
    2、ts是一个List<Task<string>>类型的列表,用于存储并发执行的任务。执行结果:
    


        
        可以看到结束时间很快,当waitall结速时,所有结果,状态都是出来了。它们的ID各不同,即由不同的异步线程执行,
    
    3、上面ID有7,那么线程池里有多少线程呢?
        对于 C# 线程池的线程数量,线程池有一个默认的最小线程数和最大线程数。默认情况下,最小线程数等于处理器的核心数,而最大线程数则是根据系统的配置和资源动态调整的。

        线程池的最小线程数可以通过 ThreadPool.GetMinThreads 方法来获取。而最大线程数可以通过 ThreadPool.GetMaxThreads 方法来获取。这些值是根据应用程序的需求和系统资源的限制来动态调整的,所以可能会有所不同。

        至于显示的线程 ID 为 7,这是因为系统上的线程池正在处理并行任务,同时分配了多个线程来执行这些任务。线程池会为每个线程分配一个唯一的 ID,因此每次显示的线程 ID 可能会有所不同。

        注意,具体的线程池行为会受到操作系统和运行时环境的影响,因此在不同的环境中结果可能会有所不同。如果有特定的线程池需求,可以通过 C# 的相关方法和属性来进行自定义配置。


四、WaitAny

        private void BtnWaitAny_Click(object sender, EventArgs e)//同步等待任一完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAny开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() =>{return book.Search();}));}Task.WaitAny(ts.ToArray());//ts任务集合中任一任务完成,调用线程就继续向后执行。foreach (var t in ts){AppendLine($"{t.Id}.{t.Status}");//a}sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


    
    1、Task.WaitAny用于等待多个异步任务中的任意一个完成。这对于同时执行多个任务,但只需要其中一个首先完成时非常有用。

        Task.WaitAny方法接受一个Task对象的数组作为参数,并等待数组中任意一个任务完成。当一个任务完成时,该方法立即返回,并返回完成的任务的索引。
        Task.WaitAny不会阻塞整个程序,只会阻塞当前的线程,直到数组中的任意一个任务完成。
        当你调用Task.WaitAny方法时,它不会等待所有任务都完成。相反,它只等待第一个完成的任务,然后立即返回。
        如果数组中没有任务完成,Task.WaitAny方法将阻塞当前线程,直到有任务完成。
        返回的索引是基于零的索引,表示完成的任务在任务数组中的位置。如果没有任务完成,该方法将返回-1。
            
    
    2、a处task.status是RanToCompletion表示什么意思?
        在C#的Task Parallel Library (TPL)中,Task.Status是一个属性,它提供了关于任务当前状态的信息。Task.Status可能枚举值如下:
            TaskStatus.Created: 表示任务刚刚被创建,但还没有开始执行。
            TaskStatus.Running: 表示任务正在执行。
            TaskStatus.RanToCompletion: 表示任务已经成功完成。
            TaskStatus.Faulted: 表示任务由于异常而失败。
            TaskStatus.Canceled: 表示任务被取消。
        所以,如果 Task.Status 是 RanToCompletion,这意味着任务已经成功完成并且没有出现任何错误。
        
        
    3、结果:
     


        
        因为是waitany是同步等待第一个完成的,就向下继续执行。所以a处不能显示结果(t.Result)。一旦用了t.Result它又会同步阻塞并等待当前t任务返回结果。因此这里用来显示各个任务的执行状态,可以看到有些在运行,有些已经完成。
    
    
    4、按完成时间的先后顺序显示信息:

        private async void BtnWaitAny_Click(object sender, EventArgs e)//同步等待任一完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAny开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() =>{return book.Search();}));}int idx = 0;while (ts.Count > 0){Task<string> com = await Task.WhenAny(ts);AppendLine($"{++idx}.{com.Result}===={DateTime.Now.TimeOfDay}");ts.Remove(com);}sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


        结果:
      


        
        上面使用异步等待任一先完成whenany就执行显示信息,同时删除这个任务,直到这些任务全部删除,也即所有任务全部完成。
        
        
        如果写成:

        private async void BtnWaitAny_Click(object sender, EventArgs e)//同步等待任一完成{List<Task<string>> ts = new List<Task<string>>();Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("WaitAny开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];ts.Add(Task.Run(() =>{return book.Search();}));}int idx = 0;await Task.WhenAll(ts.ToArray()).ContinueWith((t) =>{InvworkAppendLine($"{++idx}.{t.Result}===={DateTime.Now.TimeOfDay}");//a});sw.Stop();AppendLine($"WaitAll完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}


        因为WhenAll是异步线程执行,所以不能用AppendLine来操作UI线程的控件,这里用InvworkAppendLine委托操作UI线程的控件。
        上面a处的t只能是最后完成的任务,所以结果为:
                WaitAny开始...
                1.System.String[]====14:02:02.0183909
                WaitAll完成:2.08秒

        只有一条信息,也就是最后一个任务完成的信息。因此,需要在contiuewith进行循环:

        await Task.WhenAll(ts.ToArray()).ContinueWith((t) =>{foreach (var item in ts){InvworkAppendLine($"{++idx}.{item.Result}===={DateTime.Now.TimeOfDay}");//a}});    


        但结果却是这样的:
                WaitAny开始...
                1.封神演义--------用时:1.001====14:03:23.5000157
                2.三国演义--------用时:2.012====14:03:23.5030043
                3.水浒传---------用时:1.001====14:03:23.5030043
                4.西游记---------用时:1.001====14:03:23.5030043
                5.聊斋志异--------用时:1.001====14:03:23.5030043
                6.儒林外史--------用时:2.012====14:03:23.5030043
                7.隋唐演义--------用时:1.001====14:03:23.5030043
                WaitAll完成:2.1秒    

        明显的,它是所有任务完成后,再去取每个的耗时,最后的时间记录是循环时的时间并不是真正完成的时间。所以这个时间几乎紧贴。无论如何,只要用了whenall它是所有完成后才进行记录,就错过了每个任务完成就记录的时间,所以用whenall就是错误的。


    
五、死锁

        private void BtnDeadLock_Click(object sender, EventArgs e)//死锁{Stopwatch sw = Stopwatch.StartNew();txtInfo.Clear();AppendLine("Wait开始...");for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];var idx = i + 1;var task = book.SearchAsync();AppendLine($"{idx}.{task.Result}");//a}sw.Stop();AppendLine($"Wait完成:{Convert.ToSingle(sw.ElapsedMilliseconds) / 1000}秒");}//book类中方法public async Task<string> SearchAsync(){Stopwatch sw = Stopwatch.StartNew();await Task.Delay(Duration * 1000);//bsw.Stop();return Result(sw.ElapsedMilliseconds);//c}


    
    1、为什么async的返回类型都不能使用void?(上面用的Task<string>)
        因为有返回类型时,以便对当前任务进行判断,或对该任务的后续再次加工操作。
        使用void作为异步方法的返回类型会导致以下问题:
        (1)无法使用await等待异步方法完成:使用await关键字可以等待一个异步方法完成并继续执行后续代码。但是,如果异步方法的返回类型是void,则无法使用await等待其完成,因为不能将void类型传递给await。

            public async void DoSomethingAsync()//c 这里使用了async void{await Task.Delay(1000); // 模拟异步操作,等待1秒钟Console.WriteLine("Async operation completed.");}// 调用异步方法DoSomethingAsync();//aConsole.WriteLine("Method called.");//b// 输出结果:// Method called.// Async operation completed.


        上面a处调用了c处的async void,它是异步执行,无法知道它什么时间完成,犹如一个脱了缰绳的狗,你无法预知它什么回家(输出结果),导致输出的顺序不是预期的。        (2)无法捕获异步方法中的异常:当异步方法的返回类型是Task或Task<T>时,可以在调用异步方法时使用try-catch块捕获方法中发生的异常。但是,如果返回类型是void,则无法通过异常处理方式捕获异步方法中的异常。

            public async void DoSomethingAsync(){await Task.Delay(1000); throw new Exception("Something went wrong.");//假定出错}// 调用异步方法try{DoSomethingAsync();}catch (Exception ex){Console.WriteLine("Error: " + ex.Message);}// 输出结果:// Unhandled exception: System.Exception: Something went wrong.


        由于异步方法的返回类型是void,无法使用try-catch块捕获异步方法中的异常。因此,发生的异常会成为未处理的异常,导致程序崩溃或无法正常处理异常情况。
        本质上:
        异步方法的异常不会直接返回给调用线程,而是在Task对象中捕获。当使用await等待异步方法完成时,await会检查Task对象的状态,如果其中包含了异常,await会抛出该异常,然后可以在调用代码中使用try-catch块捕获它。也即:当异步方法抛出异常时,异常会被捕获并封装在Task对象中。然后,Task对象会在等待它的代码中传播异常。

        public async Task<int> DoSomethingAsync(){await Task.Delay(1000);throw new Exception("Something went wrong.");}try{int result = await DoSomethingAsync();Console.WriteLine("Result: " + result);}catch (Exception ex){Console.WriteLine("Error: " + ex.Message);}


        上面可以直接捕获到异步,而不会导致程序崩溃。        (3)无法获取异步方法的执行结果:异步方法的返回类型是用于包装异步操作的Task或Task<T>,其中包含了异步操作的执行结果。如果返回类型是void,则无法直接获取异步方法的执行结果。

        public async void DoSomethingAsync(){await Task.Delay(1000); Console.WriteLine("Async operation completed.");}// 调用异步方法DoSomethingAsync();Console.WriteLine("Method called.");// 输出结果:// Method called.// Async operation completed.


        修改一下,通过返回的task就能得到最后异步执行的结果、状态等。

        public async Task<int> DoSomethingAsync(){await Task.Delay(1000); Console.WriteLine("Async operation completed.");return 42;}// 调用异步方法Task<int> task = DoSomethingAsync();Console.WriteLine("Method called.");// 等待异步方法完成并获取结果int result = await task;Console.WriteLine("Result: " + result);// 输出结果:// Method called.// Async operation completed.// Result: 42


        

        使用Task或Task<T>作为返回类型可以解决上述问题,使得异步方法可以更好地集成到异步编程模型中,并允许你在调用异步方法时等待其完成、捕获异常和获取执行结果。因此,推荐在异步方法中使用Task或Task<T>作为返回类型,而不是void。
                
    
    2、为什么在UI中各控件是用的async void呢?
        在UI编程中,很多事件处理程序或回调方法需要使用async void作为返回类型。这是因为在许多UI框架中,例如WPF、Windows Forms和ASP.NET,它们提供了一种异步编程模型,允许在UI线程上执行异步操作。
        在这些UI框架中,UI元素的事件处理程序通常需要返回void类型,因为它们的返回值没有特定的用途。同时,这些事件处理程序通常需要异步执行,以避免在UI线程上进行耗时的操作,从而保持UI的响应性。

        然而,async void方法有一些潜在的问题需要注意。由于async void方法无法等待其完成,也无法捕获其中发生的异常,因此在使用时需要格外小心。如果async void方法出现异常,它可能会导致应用程序崩溃或产生意外的行为。

        为了解决这个问题,UI框架通常提供了一些错误处理机制,例如提供UnhandledException事件来捕获async void方法中的异常。开发者可以在这些错误处理机制中进行适当的异常处理,以确保应用程序的稳定性和可靠性。

        尽管在UI编程中可以使用async void方法来简化异步逻辑的书写,但在其他场景中,建议使用async Task或async Task<T>作为异步方法的返回类型,以便能够更好地处理异步操作,等待其完成,并捕获其中发生的异常。
            
    
    
    3、经典的死锁模型:
        在C#中,典型的死锁情况可以发生在主程序等待子程序的结果,而子程序又在等待主程序的空闲状态。这种情况通常称为"上下文死锁"或"异步死锁",发生在使用await和Task.Wait(或Task.Result)组合时。
        
        上面a处用task.Result是一个同步等待子程序的结果操作,它会阻塞主线程,直到子程序SearchSync返回c处结果为止。
        而此时子程序SearchAsync在b处完成异步等待后,因为默认的ConfigureAwait为true(Task不加此参数就是默认true),所以在b处异步线程并不会向下执行,而是试图将控制切换到UI线程或调用线程上去。但是,调用线程是主程序,主程序又在等子方法向下执行到c处,以便返回结果。
        这样主程序等待子程序c处的结果,子程序在b处期待主程序空闲下来,两个相互等,相互累,形成死锁。
        
    4、死锁的解决办法
        
        (1)使用ConfigureAwait(false): 在主程序调用MainMethod时,可以在await语句中使用ConfigureAwait(false)来禁用上下文切换。这样可以避免造成上下文死锁,因为等待期间不会回到主线程上下文。
        唯一要注意的是,如果在子程序中异步线程涉及要操作UI,请用委托。

        public async Task<string> SearchAsync(){Stopwatch sw = Stopwatch.StartNew();await Task.Delay(Duration * 1000).ConfigureAwait(false);sw.Stop();return Result(sw.ElapsedMilliseconds);}        


        
        (2)使用ContiueWith():让异步线程继续执行下去。在主程序中修改:

        for (int i = 0; i < Data.Books.Count; i++){var book = Data.Books[i];var idx = i + 1;Task task = book.SearchAsync().ContinueWith((t) =>{InvworkAppendLine($"{idx}.{t.Result}");//a});}


        ContinueWith执行线程将选定默认的TaskContinuationOptions,即如果没有指定TaskScheduler,在没有完成状态的线程上调用的ContinueWith处理程序将在相同的线程上执行。因此,ContinueWith中的语句会在异步操作所在的后台线程上执行。
        因此,SearchAsync中task的configureawait是true或false都是没有影响的。book.SearchAsync().ContinueWith中的代码会在异步操作所在的后台线程上执行,而不会回到UI线程。
        注意,如果想在异步操作完成后在UI线程上更新界面,可能需要确保使用BeginInvoke或使用await关键字在UI线程上执行更新操作,以确保正确的线程同步和避免潜在的跨线程问题。因此上面的InvworkAppendLine不能更改为AppendLine(要操作UI),而使用委托。

       (3)主线程再开子线程,用子线程来等待结束,这样主线程就空闲了

 

        private CancellationTokenSource cts;private Task task;private void BtnStart_Click(object sender, EventArgs e){cts = new CancellationTokenSource();CancellationToken ct = cts.Token;int idx = 0;task = Task.Run(async () =>{while (!ct.IsCancellationRequested){await Task.Delay(200);Invoke(new Action(() =>{listBox1.Items.Add(idx++.ToString("000"));listBox1.TopIndex = listBox1.Items.Count - 1;}));}}, ct);}private void BtnStop_Click(object sender, EventArgs e){cts.Cancel();listBox1.Items.Add("任务结束");listBox1.TopIndex = listBox1.Items.Count - 1;}

     上面会死锁,于是在停止中再开一个子线程,为了防止后面"任务结束"提前,用await来个异步等待。

        private async void BtnStop_Click(object sender, EventArgs e){cts.Cancel();await Task.Run(() =>{Task.WaitAll(task);});listBox1.Items.Add("任务结束");listBox1.TopIndex = listBox1.Items.Count - 1;}


        
    
    5、控制台一般不会发生死锁?
        控制台应用程序中通常不会发生死锁的情况。这是因为在控制台环境中,没有涉及到 UI 线程和界面更新等需要特殊注意的操作。
        死锁通常发生在多线程环境中,当两个或多个线程相互等待对方释放某个资源时,导致它们都无法继续执行。这种情况通常涉及到线程间的同步和互斥操作,如共享资源的访问、锁定等。
        在控制台应用程序中,由于通常只有单个主线程(也可以有其他子线程,但通常没有复杂的线程同步需求),线程间的竞争和同步问题相对较少。控制台程序一般不涉及复杂的多线程操作,因此死锁的风险较低。
        然而,如果在控制台应用程序中存在多个线程并涉及到资源竞争,可能会发生死锁情况。为了避免死锁,需要合理设计和管理线程间的同步操作,例如使用适当的同步方法、避免过多的资源竞争、正确处理锁定和竞争条件等。

        
        如果是控制台程序,或者一个普通的非 UI 线程,其 SynchronizationContext 为 null,那么异步任务执行完后不需要回到原有线程,也不会造成死锁。
        当控制台的SynchronizationContext为null时,无法自动切换回调的执行线程,即使ConfigureAwait(true)被使用。在这种情况下,异步操作完成后,执行的后续代码会继续在异步操作所在的线程上执行,而不会自动切换到调用线程。

        ConfigureAwait(true) 的作用是告诉编译器在异步操作完成后,尽可能地切换回调的执行上下文(例如UI线程),以便便利地进行界面更新等操作。但是,当当前上下文(如控制台)的SynchronizationContext为null时,切换上下文的机制不起作用,后续代码仍然会在原始调用线程上执行。

        如果想确保后续代码在调用线程上执行,可以使用ConfigureAwait(false),显式地指示不切换上下文。这样,无论当前的SynchronizationContext是否为null,后续代码都会在异步操作所在的线程上执行,而不进行自动切换。

        注意,控制台环境通常是单线程的,因此即使使用ConfigureAwait(true)也不会发生线程切换。在异步操作完成后,后续代码仍然会继续在同一个控制台线程上执行,而不会自动切换到其他线程。
    
    
 


http://www.ppmy.cn/news/1098493.html

相关文章

YOLOV7改进-具有隐式知识学习的Efficient解耦头

[解耦头][https://github.com/z1069614715/objectdetection_script/blob/master/yolo-improve/yolov7-DecoupledHead.py] 1、复制这些到yolo.py 2、到这 3、复制下半部分到yolo.py 4、替换这里 5、最后的加到上面的这里 6、添加 7、添加 8、V5大概一个点的提升 9、解…

【美团3.18校招真题2】

大厂笔试真题网址&#xff1a;https://codefun2000.com/ 塔子哥刷题网站博客&#xff1a;https://blog.codefun2000.com/ 最多修改两个字符&#xff0c;生成字典序最小的回文串 提交网址&#xff1a;https://codefun2000.com/p/P1089 由于字符串经过修改一定为回文串&#x…

1DM+下载器_11.2.1魔改增强版下载

1DM「原&#xff1a;IDM」下载器是一款安卓端的下载工具&#xff0c;多语言解锁版直安装版本&#xff0c;号称是目前 Android 平台最快、最先进的下载管理器应用「支持通过Torrent下载」&#xff0c;而这个版本是改线程的最新idm版本&#xff0c;可用来下载视频、音乐、电影、T…

【C++ • STL】一文带你走进string

文章目录 一、STL简介二、标准库中的string类三、string类的常用接口说明2.1 string类对象的常见构造2.2 string类对象的访问及遍历操作2.2.1 元素访问2.2.2 迭代器 2.3 string类对象的容量操作2.4 string类对象的修改操作2.5 string类非成员函数 四、总结 ヾ(๑╹◡╹)&#x…

PHP自己的框架2.0版本目录结构和命名空间自动加载类(重构篇一)

目录 1、目录结构演示效果 2、搭建目录结构&#xff0c;以及入口public->index.php 3、引入core下面core->base.php 4、自动加载实现core->fm->autoload.php 5、框架运行文件core->fm->core.php 6、最终运行index.php结果 1、目录结构演示效果 2、搭…

代码随想录二刷day18

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣513. 找树左下角的值二、力扣112. 路径总和三、力扣113. 路径总和 II四、力扣106. 从中序与后序遍历序列构造二叉树五、力扣105. 从前序与中序遍历序列…

MySQL5.7 在Window平台安装

一、下载 在MySQL的官网下载安装包 MySQL :: Download MySQL Community Serverhttps://dev.mysql.com/downloads/mysql/ 这里我选择的是x64的ZIP安装包&#xff1b;点击Download下载 这里我选择的是直接开始下载 二、解压与初始化 将下载好的安装包解压&#xff0c;这里我解…

Spring全家桶相关注解总结

spring相关 Controller 【控制器】效验有效参数的合法性&#xff08;相当于安检系统&#xff09; Service 【服务】业务组装&#xff08;客服中心&#xff09; Repository 【数据持久层】实际业务处理&#xff08;实际办理的业务&#xff09; Component 【组件】工具类…

LED屏幕电流驱动设计原理

LED电子显示屏作为户外最大的应用产品&#xff0c;是大型娱乐&#xff0c;体育赛事&#xff0c;广场大屏幕等场所不可或缺的产品&#xff0c;从单双色简单的文字展示到今天的高清全彩&#xff0c;显示屏的技术一直都在进步&#xff0c;全球80%的LED电子显示屏皆产自于中国。显示…

数据结构和算法之插入排序

一、插入排序 插入排序是一种简单直观的排序算法。它的原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。 #mermaid-svg-v2YbPqchr8qWCPvn {font-family:"trebuchet ms",verdana,arial,san…

第16章_多版本并发控制MVCC

1. 什么是MVCC MVCC &#xff08; Multiversion Concurrency Control &#xff09;&#xff0c;多版本并发控制。顾名思义&#xff0c; MVCC 是通过数据行的多个版本管理来实现数据库的 并发控制 。这项技术使得在 InnoDB 的事务隔离级别下执行 一致性读 操作有了保证。换…

单例模式(饿汉式单例 VS 懒汉式单例)

所谓的单例模式就是保证某个类在程序中只有一个对象 一、如何控制只产生一个对象&#xff1f; 1.构造方法私有化&#xff08;保证对象的产生个数&#xff09; 创建类的对象&#xff0c;要通过构造方法产生对象 构造方法若是public权限&#xff0c;对于类的外部&#xff0c;可…

感应电动机

引言 感应电动机,这个名字对于普通人来说可能有些陌生,但它在我们的日常生活和工作中占据着举足轻重的地位。从各类电器设备到工业生产设备,感应电动机的应用广泛而深入。了解感应电动机的种类和主要结构有助于我们更好地理解其工作原理,从而为我们的生活和工作带来更多便利…

汽车电气架构

文章目录 现阶段的汽车电气架构高压混合动力系统电子架构集成化&#xff08;E/E Architecture Integration&#xff09;车载以太网技术软件定义汽车 现阶段的汽车电气架构 当前最先进的汽车电气架构主要有以下几种&#xff1a; 48伏微混合系统&#xff08;48V Mild Hybrid Sys…

spring---第四篇

系列文章目录 文章目录 系列文章目录一、解释下Spring支持的几种bean的作用域。二、Spring框架中的单例Bean是线程安全的么?一、解释下Spring支持的几种bean的作用域。 singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该 对象的生命周期…

亚运会:举办次数最多的是泰国,观众满意度衡量赛事成功情况

本文由群狼调研&#xff08;长沙第三方评估&#xff09;整理出品&#xff0c;欢迎转载&#xff0c;请注明出处。8日起至9月20日&#xff0c;杭州第19届亚运会火炬传递启动&#xff0c;杭州亚运会是放开后的首个盛会&#xff0c;亚洲的健儿齐集于此&#xff0c;同场竞技&#xf…

mac文件已损坏,无法打开-解决方案

mac文件已损坏&#xff0c;无法打开-解决方案 解释 核心逻辑就是苹果电脑自身的安全限制&#xff0c;限制了未知认知逻辑的应用。 解决方案 打开终端运行下面的命令&#xff0c;按回车后输入密码执行。 sudo xattr -r -d com.apple.quarantine /Applications/Navicat\ Pre…

例举onekey一键还原如何使用

onekey一键还原怎么使用呢​​​​​​​?随着数字化的发展&#xff0c;现在电脑已成为人们工作学习娱乐的必备工具&#xff0c;想要放心的使用电脑&#xff0c;不仅需要杀毒软件&#xff0c;还需要一款一键还原软件。接下来&#xff0c;我就教大家如何使用onekey一键还原。还…

插入排序(Java实现)

前言 稳定性&#xff1a;如果一个排序是稳定的&#xff0c;是可以变成不稳定的&#xff0c;此时这个排序归结为稳定&#xff0c;但是如果这个排序本身是不稳定的&#xff0c;是不可以变成稳定的&#xff0c;此时这个排序是不稳定的。 过程&#xff1a;如果数组中只有一个元素&a…

MySQL——连接查询

2023.9.4 连接查询相关sql92语句笔记&#xff1a; #连接查询。 又称多表查询&#xff0c;当查询的字段来自多个表时&#xff0c;就会用到连接查询。 #等值连接 /* ①多表等值连接的结果为多表的交集部分 ②n表连接&#xff0c;至少需要n-1个连接条件 ③多表的顺序没有要求 ④一…
最新文章