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

news2024/11/26 7:51:36

   
一、文件构成

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/969640.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 7 第八讲 集群模式(cluster)架构篇

集群架构 Redis 集群架构图 集群定义 Redis 集群是一个提供在多个Redis节点间共享数据的程序集;Redis集群可以支持多个master 应用场景 Redis集群支持多个master,每个master又可以挂载多个slave读写分离支持数据的高可用支持海量数据的读写存储操作集群自带Sentinel的故障…

看懂UML类图

UML 统一建模语言(Unified Modeling Language&#xff0c;UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言&#xff0c;是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具&#xff0c;独立于任何具体程序设计语言。 类的表示 首先看那个…

ROS2中如何从欧拉角转换成四元素

ROS1中使用from tf.transformations import quaternion_from_euler导入quaternion_from_euler()即可调用。而ROS2中默认没有安装&#xff0c;需要单独安装一下ros-galactic-tf-transformations(我使用的ROS2是galactic&#xff0c;根据版本名不同做相应修改即可)&#xff1a; …

Python GUI入门——tkinter编辑框、复选框、下拉菜单和按钮文本框的使用

简介 接上一次的tkinter编写界面相关内容&#xff0c;丰富点常用控件的方法&#xff0c;学会了这些控件布局和相关方 法属性&#xff0c;能够满足日常小工具的制作需求了。 搭建的界面框架如下图所示&#xff0c;功能可以自己添加。 界面代码 # -*- coding: utf-8 -*- impor…

第五章 树于二叉树 七、树和森林的遍历(广度优先遍历、深度优先遍历)

1、树的遍历 树是一种递归定义的数据结构&#xff0c;所以我们可以使用递归实现遍历。 &#xff08;1&#xff09;先根遍历&#xff08;最先访问根节点&#xff09;&#xff08;深度优先遍历&#xff09; 1.使用孩子兄弟表示法将其转化为二叉树的形式。 2.使用先序遍历二叉树…

面试2:通用能力

15丨如何做好开场&#xff1a;给自我介绍加“特效 第一层&#xff0c;满足面试官对信息的期待 这是对自我介绍的基本要求&#xff0c;把个人信息、主要经历、经验和技能有条理地组织起来&#xff0c; 有逻辑地讲出来。需要找出多段经历的关联性和发展变化&#xff0c;形成连…

分布式事务之 Seata 的部署和集成

文章目录 一、部署Seata的tc-server1.下载2.解压3.修改配置4.在nacos添加配置5.创建数据库表6.启动TC服务 二、Docker 中跑 Seata三、微服务集成seata1.引入依赖2.修改配置文件 四、TC服务的高可用和异地容灾1.模拟异地容灾的TC集群2.将事务组映射配置到nacos3.微服务读取nacos…

Linux下go环境安装、环境配置并执行第一个go程序

一、安装 1.Golang对Linux的内核版本要求 GO对Linux内核版本最低要求是 2.6.23&#xff0c;对应要求操作系统版本是&#xff1a; RHEL 6.0CentOS 6.0即&#xff0c;不支持 (RHEL 和 CentOS) 的 (4.x or 5.x)。2.下载golang的代码版本 Golang的官网下载地址&#xff1a;https:…

Presto 之Pipeline

一. 前言 我们知道在Presto中有个叫Pipeline的概念&#xff0c;Pipeline其实就是一条包含各个算子的流水线&#xff0c;如下所示。本文主要介绍在Presto中&#xff0c;Pipeline是如何划分的。 二. Pipeline 在Presto中&#xff0c;一个Stage中包括一个或者多个Pipeline&#xf…

JavaWeb项目基础配置

这里写目录标题 前言1.pom.xml2.webxml3.连接JDBC4.常用代码4.1获取时间4.2实现上传文件需要用到第三方库fileupload 常用快捷键常用操作Tomcat基础操作&#xff1a; 前言 本文主要讲述了 1.pom.xml <?xml version"1.0" encoding"UTF-8"?><pro…

9.2QTday4作业

1 闹钟 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QtDebug> #include <QLabel> //标签类 #include <QLineEdit> //行编辑器类 #include <QPushButton> //按钮类 #include <QTextEdit> //文本编辑器…

STM32定时器定时及其应用

STM32定时器定时及其应用 定时器概述☆定时器相关配置CubeMX工程配置及程序实现固件库程序设计及实现 定时器概述 1. 工作原理 使用精准的时基&#xff0c;通过硬件的方式&#xff0c;实现定时功能。定时器核心就是计数器 2. 定时器分类   基本定时器&#xff08;TIM6~TIM7…

SiameseNet实战中文文本匹配任务

引言 本文我们通过SiameseNet模型来完成中文文本匹配任务&#xff0c;其中包含了文本匹配任务一般套路&#xff0c;后续只需要修改实现的模型。 数据准备 数据准备包括 构建词表(Vocabulary)构建数据集(Dataset) 本次用的是LCQMC通用领域问题匹配数据集&#xff0c;它已经…

RK3568-android11-适配ov13850摄像头

硬件连接 主要分为两部分: mipi接口:传输摄像头数据 i2c接口:配置摄像头和对焦马达芯片寄存器相关驱动 |-- arch/arm64/boot/dts/rockchip DTS配置文件 |-- drivers/phy/rockchip/|-- phy-rockchip-mipi-rx.c mipi dphy 驱动 |-- drivers/media||-- platform/rockchip/isp1…

20230903-闹钟

app.cpp #include "app.h" #include "ui_app.h" int k1 true;APP::APP(QWidget *parent):QWidget(parent),ui(new Ui::APP) {ui->setupUi(this);this->resize(380,300);this->setStyleSheet("background-color:cyan;");//设置样式spe…

关于指针的一些练习(1)

1. int main() {int a[3][2] { (0,1),(2,3),(4,5) };int* p;p a[0];printf("%d", p[0]);return 0; } 解析&#xff1a;a是一个3行2列的二维数组&#xff0c;对他进行初始化时大括号里面是逗号表达式&#xff0c;根据逗号表达式可以得到a数组中元素为1&#xff0c…

知识图谱推理研究综述9.3

综述分类 根据样本量大小的不同&#xff0c;将知识图谱推理方法分为多样本推理、少样本推理和零与单样本推理 KG定义&#xff1a;&#xff08;Y&#xff09; 知识图谱是以图的形式表示真实世界的实体与关系之间关系的知识库。 具体来说知识图谱是通过将应用数学、图形学、信…

培训机构如何利用小程序提升服务质量

近年来&#xff0c;小程序成为了许多企业和机构进行线上业务拓展的新方式。对于培训机构来说&#xff0c;构建一个具有吸引力的小程序可以帮助他们更好地与学员进行互动和沟通&#xff0c;并提供更便捷的学习服务。那么&#xff0c;如何使用第三方制作平台来构建一个具有吸引力…

线性代数的学习和整理18:什么是维度,什么是秩?关于秩的各种定理 (未完成)

目录 1 矩阵的秩 矩阵的秩 2 求秩的方法 矩阵的维度秩 矩阵的维度 向量的模&#xff0c;矩阵的模-没有把&#xff0c;难道是面积&#xff1f; 矩阵的平直概念 5 矩阵的初等变换&#xff08;矩阵等价概念的引出&#xff09; 0 问题引出&#xff1a;什么是秩&#xff1f;…

UG\NX CAM二次开发 查询工序所在的几何组TAG UF_OPER_ask_geom_group

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 查询工序所在的几何组TAG UF_OPER_ask_geom_group 效果: 代码: void MyClass::do_it() { int count=0;tag_t * objects;UF_UI_ONT_ask_selected_nodes(&count, &objects);for (in…