(22)Task.Delay与Thread.Sleep,Wait与When,复习

    

    
一、启动线程的多种方式


    1、New Task()
    2、Task.Run();
    3、TaskFactory.StartNew();
    4、Task.Factory.StartNew();

        //方式一Task task = new Task(() =>{Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");});task.Start();//方式2Task.Run(() =>{Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");});//方式3TaskFactory tf = new TaskFactory();tf.StartNew(() =>{Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");});//方式4Task.Factory.StartNew(() =>{Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");});   

 


二、Thread.Sleep()与Task.Delay()的区别


    Thread.Sleep() 和 Task.Delay() 是两种不同的方法,用于在代码中添加延迟或暂停的操作。

    1. Thread.Sleep() 方法
        是在当前线程上阻塞指定的时间。它会使当前线程进入休眠状态,不会执行任何其他操作,直到指定的时间过去。这个方法通常在单线程或多线程的情况下使用。
       Thread.Sleep(1000);//使当前线程休眠 1 秒
       

    2. Task.Delay() 方法
        是一个异步方法,它会创建一个延迟指定时间的任务。它不会阻塞当前线程,而是返回一个表示延迟操作的 Task 对象。可以使用 await 关键字等待延迟操作完成。
       await Task.Delay(1000);//将异步延迟 1 秒
       或者,如果在同步上下文中使用,可以使用 Task.Delay().Wait() 等待延迟操作完成:
       Task.Delay(1000).Wait();
       使用 Task.Delay() 的好处是,它可以在异步编程模型中更好地与其他异步操作一起使用,而不会阻塞线程。这对于 UI 线程或其他需要响应性的情况特别有用。

        总结来说,Thread.Sleep() 是一个同步方法,会阻塞当前线程,而 Task.Delay() 是一个异步方法,不会阻塞当前线程,可以更好地与异步编程模型一起使用。
    
    
    3、Await Task.Delay()与Task.Delay().Wait()的区别
        Task.Delay(1000).Wait() 和 await Task.Delay(1000) 在功能上是相同的,它们都会在延迟指定时间后继续执行代码,但在使用上有区别:

        (1)Task.Delay(1000).Wait() 是一个阻塞的同步调用。它会阻塞当前线程,直到延迟操作完成。这意味着在调用 Wait() 之后,当前线程将无法执行其他操作,直到延迟时间过去。

        (2)await Task.Delay(1000) 是一个异步调用。它会在延迟时间内返回一个未完成的任务,并允许当前线程继续执行其他操作。使用 await 关键字等待延迟操作完成后,代码将在延迟时间过去后继续执行。        注意:为了使用 await,它必须在一个异步方法中调用,或者使用 Task.Run() 创建一个新的线程来执行异步操作。但都需要前面的async配合。

       async Task MyMethod(){// 在异步方法中使用 awaitawait Task.Delay(1000);// 延迟操作完成后继续执行}// 或者在新的线程中使用 Task.Run() 创建异步操作Task.Run(async () =>{await Task.Delay(1000);// 延迟操作完成后继续执行});


        总结,Task.Delay(1000).Wait() 是一个阻塞的同步调用,而 await Task.Delay(1000) 是一个异步调用,可以在延迟时间内继续执行其他操作。使用 await 可以更好地与异步编程模型一起使用,并提供更好的响应性。
        Task.Delay().Wait()的确是一种阻塞当前线程的写法,与异步编程的初衷相悖,这种写法在异步编程中通常是不推荐的,因为它会阻塞线程,导致资源的浪费和性能的降低。在异步编程中,我们通常使用 await Task.Delay() 来实现延迟操作,而不是使用 Task.Delay().Wait()。
    


三、多线程应用的场景


    多线程可以在许多场景中应用,特别是在以下情况下:
    1. **并行处理**:
    当有一些相互独立的任务需要同时执行时,可以使用多线程来实现并行处理,以提高整体的执行效率。例如,对于大规模数据的处理、图像/视频处理、并行计算等场景,多线程可以将任务分配给多个线程并同时执行。

    2. **响应性用户界面**:
    在需要保持用户界面的响应性的情况下,可以使用多线程来处理耗时的操作,以避免阻塞用户界面。例如,在进行网络请求、数据库查询或其他耗时的操作时,可以将这些操作放在单独的线程中执行,使用户界面保持流畅。

    3. **并发访问共享资源**:
    当多个线程需要同时访问共享资源(如共享变量、文件、数据库等)时,可以使用多线程来实现并发访问。通过使用锁、互斥量、信号量等同步机制,可以确保多个线程对共享资源的访问是安全的。

    4. **后台任务处理**:
    在需要在后台执行长时间运行的任务时,可以使用多线程来执行这些任务,以避免阻塞主线程。例如,在进行文件下载、数据同步、定时任务等场景中,可以将这些任务放在后台线程中执行。

    5. **并发编程**:
    在需要处理大量并发请求或事件的情况下,可以使用多线程来实现并发编程。例如,在网络服务器、消息队列处理、并发事件处理等场景中,多线程可以同时处理多个请求或事件。

    注意,多线程编程需要小心处理线程安全性、资源竞争、死锁等问题。合理地使用同步机制、线程池、异步编程等技术,可以帮助避免这些问题。此外,使用并发集合、并发字典等线程安全的数据结构,也可以简化多线程编程。
    


四、几个等待


    1、复习:带Wait的等待
    
        用于等待任务完成的方法,包括 Task.Wait()、Task.WaitAny()、Task.WaitAll() 和 Task.WaitOne()。这些方法的区别如下:        (1)Task.Wait() 方法:
        该方法会阻塞当前线程,直到任务完成。如果任务抛出异常,Wait() 方法也会抛出相同的异常。Wait() 方法返回为void,异常则任务失败,反之成功。
        使用实例调用:task.Wait();
        
        (2)Task.WaitAny() 方法:
        该方法会阻塞当前线程,直到其中任意一个任务完成。它接受一个 Task 数组或可枚举对象作为参数,并返回已完成任务的索引。如果其中任意一个任务抛出异常,WaitAny() 方法也会抛出相同的异常。返回值为int,表示任务数组的索引,由此可知最先完成的任务。
        使用静态方法调用:Task.WaitAny(...)

        Task[] tasks = new Task[3];tasks[0] = Task.Run(() =>{Thread.Sleep(1000);Console.WriteLine("任务0完成!");});tasks[1] = Task.Run(() =>{Thread.Sleep(3000);Console.WriteLine("任务1完成!");});tasks[2] = Task.Run(() =>{Thread.Sleep(2000);Console.WriteLine("任务2完成!");});int intStart = Task.WaitAny(tasks);//返回最先完成任务的索引Console.WriteLine($"任务{intStart}先完成!");


        结果:
            任务0完成!
            任务0先完成!
            任务2完成!
            任务1完成!

        (3)Task.WaitAll() 方法
        该方法会阻塞当前线程,直到所有任务都完成。它接受一个 Task 数组或可枚举对象作为参数。如果其中任意一个任务抛出异常,WaitAll() 方法也会抛出相同的异常。它返回是void,所以无法确定谁先谁后完成。
        使用静态方法调用:Task.WaitAll(...)
        
        警告:
        WhenAll()与WhenAny()都是静态方法,参数就是任务数组。如果没有带参数,将不会等待,直接向下执行!!!

        (4)WaitOne() 方法:
        是 ManualResetEvent 类中的方法,用于等待信号的触发。
        ManualResetEvent 是一个同步等待句柄,它可以通过 Set() 方法设置信号,通过 Reset() 方法重置信号,并且可以通过 WaitOne() 方法等待信号的触发。在异步编程中,可以使用 ManualResetEvent 来实现一些同步操作。    
    
    
    2、WhenAll()介绍
        Task.WhenAll() 方法是 .NET 中的一个异步方法,它接受一个 Task 数组或可枚举对象作为参数,并返回一个新的任务,该任务将在所有的输入任务都完成时变为已完成状态。

        当你想要等待多个任务同时完成时,可以使用 Task.WhenAll() 方法。它会等待所有的任务都完成,然后返回一个新的任务,该任务将在所有输入任务都完成时变为已完成状态。

        (1)Task.WhenAll() 方法接受一个 Task 数组或可枚举对象作为参数。你可以将所有要等待的任务放入一个数组中,并将该数组作为参数传递给 WhenAll() 方法。

        (2) 返回的任务将在所有输入任务都完成时变为已完成状态。这意味着,只有当所有的任务都完成时,返回的任务才会完成。        (3)返回的任务的结果类型是 Task 数组,其中每个元素对应于输入任务数组中的一个任务。你可以通过访问返回任务的 Result 属性来获取每个任务的结果。

        Task<int> task1 = Task.Run(() =>{ return 32; });Task<int> task2 = Task.Run(() =>{Thread.Sleep(1000);return 43;});Console.WriteLine("前:" + DateTime.Now);Task<int[]> t = Task.WhenAll(task1, task2);//aConsole.WriteLine("后:" + DateTime.Now);int[] ns = await t;//bforeach (int n in ns){Console.WriteLine(n);}   

     
        上面b处,是用await t取得结果后,再从内层取得返回值,是异步不会阻塞当前线程。如果用t.Result来代替await t,将是一个同步且阻塞当前线程。一般使用await t,这样可以充分利用异步编程的特性,提高程序的性能和响应能力。
        
        (4) 如果输入任务数组中的任何一个任务失败(即抛出了异常),返回的任务也将失败,并且会抛出一个聚合异常,其中包含了所有任务的异常信息。        (5)你可以使用 await 或 ContinueWith() 等方法来等待返回的任务的完成。当返回的任务完成时,表示所有的输入任务都已经完成。

        Task<int> task1 = Task.Run(() =>{return 32;});Task<string> task2 = Task.Run(() =>{return "task2";});Task.WhenAll(task1, task2).ContinueWith((t) =>{Console.WriteLine(task1.Result);Console.WriteLine(task2.Result);});


        
        也可以使用Await来异步等待,不然的话Task.WhenAll(task1, task2)会一闪而过

        Task<int> task1 = Task.Run(() =>{return 32;});Task<string> task2 = Task.Run(() =>{Thread.Sleep(1000);return "task2";});Console.WriteLine(DateTime.Now);await Task.WhenAll(task1, task2);Console.WriteLine(DateTime.Now);Console.WriteLine(task1.Result);Console.WriteLine(task2.Result);


        
        上面用Await必须在方法前添加Async。也可改用GetAwait():

        Console.WriteLine(DateTime.Now);Task.WhenAll(task1, task2).GetAwaiter().GetResult();Console.WriteLine(DateTime.Now);Console.WriteLine(task1.Result);Console.WriteLine(task2.Result);


        
        
        问:上面Task.WhenAll(task1, task2).GetAwaiter().GetResult();能否改为Task.WhenAll(task1, task2).GetAwaiter();?
        答:不能!!
            因为GetAwaiter()相当于在该句前面添加一个Await,它只是有异步等待功能,这个功能一添加就返回了,不会等待它的执行结果。也就是这一句仍然是一闪而过,不会等待。但加了GetReslut它等待出了结果才能过,相当于等待所有任务全部完成。
            
            当调用 GetAwaiter() 方法时,它会立即返回一个 TaskAwaiter 对象,表示异步操作已经开始执行。这个方法并不会等待异步操作的实际完成,而是返回一个可用于等待操作完成的对象。
            然后,我们可以使用 GetResult() 方法来获取异步操作的结果。这个方法会阻塞当前线程,直到异步操作完成,并返回异步操作的结果。

            Console.WriteLine("前:" + DateTime.Now);TaskAwaiter ta = Task.WhenAll(task1, task2).GetAwaiter();Console.WriteLine("后:" + DateTime.Now);Console.WriteLine(ta.IsCompleted);Console.WriteLine(task1.Result);


            结果:
                前:2023/9/9 16:08:50
                后:2023/9/9 16:08:50
                False
                32
            
            所以,GetAwaiter() 方法表示异步操作已经开始执行,而 GetResult() 方法表示异步操作已经完成,并返回结果。在异步方法中,我们可以直接使用 await 关键字来等待异步操作的完成,并获取结果,而不需要显式调用这两个方法。但在同步方法中,我们需要使用 GetAwaiter().GetResult() 来等待异步操作的完成,并获取结果。
            
            
        问:为什么说Task.WhenAll(task1, task2).GetAwaiter().GetResult()要小心死锁?
        答:这是因为 GetResult() 方法是同步方法,会阻塞当前线程,直到异步操作完成,但同时也会阻塞异步操作所使用的线程。
            在某些情况下,如果我们在同一个上下文中使用 GetAwaiter().GetResult() 来等待多个异步操作的完成,而这些异步操作又依赖于同一个上下文资源(例如共享锁),那么可能会发生死锁。

            在Task.WhenAll(task1, task2).GetAwaiter().GetResult(); 中,Task.WhenAll() 方法会等待所有的任务都完成,然后返回一个新的任务,表示所有任务的完成。然后我们调用 GetAwaiter().GetResult() 方法来等待这个新的任务的完成,并获取结果。

            如果 task1 和 task2 都依赖于同一个上下文资源,并且在等待它们的完成时使用了 GetAwaiter().GetResult() 方法,那么可能会发生死锁。这是因为 GetResult() 方法会阻塞当前线程,同时也会阻塞异步操作所使用的线程,导致这两个任务无法完成。

            为了避免死锁,我们应该在异步上下文中使用异步操作,而不是使用 GetAwaiter().GetResult() 方法来等待异步操作的完成。可以使用 await Task.WhenAll(task1, task2); 来等待多个任务的完成。这样可以避免死锁,并且能够更好地利用异步操作的性能优势。
        
    
    3、Await与GetAwait的区别
        两者都用于等待异步操作完成,区别如下:

        (1)语法和使用:
        await 是一个关键字,可直接用于异步方法中,通过 await 等待一个返回 Task 或 Task<T> 类型的异步操作完成。使用 await 关键字时,编译器会自动为我们生成异步状态机,简化了异步编程的代码编写。而 GetAwaiter().GetResult() 是一个方法调用,手动等待任务完成,并获取任务的结果。。

        (2)异常处理:
        await 关键字在等待异步操作时会正确处理异常。如果异步操作抛出异常,await 会将该异常包装在 Task 或 Task<T> 中,并通过异常处理机制进行传播。相比之下,GetAwaiter().GetResult() 方法在等待异步操作时,如果异步操作抛出异常,异常将直接被抛出,而不会被包装在 Task 中。
        这意味着如果你没有在实际发生异步操作的地方使用 try-catch 块来捕获异常,异常将会中断程序的执行,而不会被返回到调用处的 Task 对象中进行处理。因此,在使用 GetAwaiter().GetResult() 方法时,确保在发生异步操作的地方使用 try-catch 块来捕获异常,以便适当地处理异常情况。

        (3)调用线程:
        await 关键字在等待异步操作完成时,会暂时释放当前线程,以允许线程去执行其他任务,避免了阻塞。而 GetAwaiter().GetResult() 方法会阻塞当前线程,直到异步操作完成,如果是在主线程中使用此方法,可能会导致界面卡顿或死锁等问题。

        (4)死锁风险:
        使用 GetAwaiter().GetResult() 方法时,如果该方法的调用和异步操作所在的上下文处于同一线程上下文中(如 UI 线程中),并且异步操作中包含需要在同一上下文中执行的代码,就有可能发生死锁。而 await 关键字会自动处理上下文切换,避免了潜在的死锁风险。        总结:
        await 关键字是 C# 异步编程的语法糖,提供了更简洁、安全和易用的方式来等待异步操作完成。GetAwaiter().GetResult() 方法是一种较底层的手动等待异步操作的方式,需要谨慎使用,避免可能的死锁和异常处理问题。
    
        另外使用await具有异常的传导性,会将异步中的异常传递到主调线程。
        这是因为 await 表达式会将异步操作的结果包装在一个 Task 对象中,并返回给主调线程。如果异步操作被取消或异常,那么 Task 对象的状态将变为已取消或异常,并且在主调线程上的 await 表达式中会抛出TaskCanceledException异常。

        CancellationTokenSource cts = new CancellationTokenSource();Task task = Task.Run(() =>{while (!cts.IsCancellationRequested){Thread.Sleep(2000);cts.Token.ThrowIfCancellationRequested();//异步中抛出取消异步//throw new Exception("人为抛出");}}, cts.Token);try{cts.Cancel();await task;//此处捕获异常}catch (Exception ex){Console.WriteLine(ex.ToString());}      

 
    
    
    3、问:上面最后一个例子中第二参数有什么用处?
        答:大多数情况下,第二参数用或不用都没有什么区别。
            目前个人发的区别有两个:
            (1)任务前取消,则异步线路不会启动。

        CancellationTokenSource cts = new CancellationTokenSource();List<Task> tasks = new List<Task>();try{for (int i = 0; i < 10; i++){int j = i;tasks.Add(Task.Run(async () =>{Console.WriteLine($"执行任务 {j}开始");if (cts.Token.IsCancellationRequested)// 检查是否应该取消任务{//cts.Token.ThrowIfCancellationRequested();// 取消任务throw new Exception("异常中断");}await Task.Delay(1000);Console.WriteLine($"执行任务 {j}结束");}, cts.Token));await Task.Delay(5);if (j == 5){cts.Cancel();//a}}await Task.WhenAll(tasks.ToArray());//b}catch (TaskCanceledException){Console.WriteLine("任务被取消");}catch (Exception ex){Console.WriteLine("其他异常:" + ex.Message);}   

         
            上面在a处,序号为5时就取消任务,所以后面的6,7,8,9任务不会启动。(如果没有这个参数,那么后面的6-9任务会启动,但不会正常结束。简单地)
            为了捕获异常,必须要用b处的await,不然异步虽然在Task中,但没有await时try无法捕捉。
            
            
            (2)有第二参数,异步中异常会传递到主调线程中捕获。
            没有第二个参数,异步中的异常直接抛出,中断程序,而不会隐忍不发传递给主调线程。
            上面2中有第二参数,所以传递回主调线程捕获。下面没有第二参数

        Task task = Task.Run(() =>{Thread.Sleep(2000);throw new Exception("人为抛出");//a});try{await task;//b}catch (Exception ex){ Console.WriteLine(ex.ToString()); }   

 
            上面在b处无法捕获异步,在a处直接抛出异常而中断程序。
        
        
    4、被忽视的async,在异步异常中调试作用。

        CancellationTokenSource cts = new CancellationTokenSource();List<Task> tasks = new List<Task>();try{for (int i = 0; i < 10; i++){int j = i;tasks.Add(Task.Run(async () =>//a{Console.WriteLine($"执行任务 {j}开始");if (cts.Token.IsCancellationRequested){//cts.Token.ThrowIfCancellationRequested();// 取消任务throw new Exception("异常中断");}if (j == 3){throw new Exception("异常中断");//b}await Task.Delay(1000);Console.WriteLine($"执行任务 {j}结束");}, cts.Token));//fawait Task.Delay(5);if (j == 5){cts.Cancel();}}await Task.WhenAll(tasks.ToArray());//c//Task.WaitAll(tasks.ToArray());//d}catch (AggregateException ee){Console.WriteLine("agg中断" + ee.Message);}catch (TaskCanceledException){Console.WriteLine("任务被取消");}catch (Exception ex){Console.WriteLine("其他异常:" + ex.Message);}


        上面注释了c和d后,b处的异常也不会抛出,主调程序也不会捕获,为什么?
        原因:
        (1)a处的async是罪魁祸首。
        当你取消async关键字时,异常会立即抛出并中断程序的执行。而当你使用async关键字时,异常会被封装在Task对象中,程序可以继续执行后续的代码,需要使用await关键字或Task.Wait()方法来等待任务的完成并捕获异常。
        
        当任务遇到异常时,如果没有使用async关键字,异常将会立即抛出并中断程序的执行。这是因为没有使用async关键字时,任务是在同步上下文中执行的,异常会直接传播到调用方,导致程序中断。

        而当你使用async关键字时,任务是在异步上下文中执行的。在异步上下文中,异常不会立即传播到调用方,而是被封装在Task对象中。这样,程序可以继续执行后续的代码,而不会中断。你可以通过await关键字或Task.Wait()方法来等待任务的完成,并捕获异常进行处理。
        
        (2)主线程(调用方)没有使用含有异常的Task,将不会捕获
        如果主线程(调用方)没有使用Task对象来等待异步任务的完成,那么它也无法捕获到异步任务中抛出的异常。这是因为异常是封装在Task对象中的,如果没有使用Task对象,异常就无法传播到主线程。
        
        为了确保主线程能够捕获到异步任务中的异常,你需要使用Task对象来等待任务的完成,并在主线程中进行异常处理。这可以通过使用Task.WaitAll或await Task.WhenAll等待任务的完成来实现。这样,如果异步任务中发生异常,它会被传播到主线程,并可以在主线程中进行异常处理。
        
    
    5、WhenAny()介绍
        Task.WhenAny() 方法是 .NET 中的一个异步方法,它接受一个 Task 数组或可枚举对象作为参数,并返回一个新的任务,该任务将在其中任意一个输入任务完成时变为已完成状态。

        当你想要等待多个任务中的任意一个完成时,可以使用 Task.WhenAny() 方法。它会等待其中任意一个任务完成,然后返回一个新的任务,该任务将在其中任意一个输入任务完成时变为已完成状态。

        (1)Task.WhenAny() 方法接受一个 Task 数组或可枚举对象作为参数。你可以将所有要等待的任务放入一个数组中,并将该数组作为参数传递给 WhenAny() 方法。

        (2)返回的任务将在其中任意一个输入任务完成时变为已完成状态。这意味着,只要有一个任务完成,返回的任务就会完成。        (3)返回的任务的结果类型是 Task<Task>,其中内部的 Task 对象表示已完成的任务。你可以通过访问返回任务的 Result 属性来获取已完成任务的结果。

        Task<int> task1 = Task.Run(() =>{ return 32; });Task<string> task2 = Task.Run(() =>{Thread.Sleep(1000);return "task2";});Task<Task> firstTask = Task.WhenAny(task1, task2);//aawait firstTask;//bTask completedTask = await firstTask;//c//Task<object> completedTask = (Task<object>)await firstTask;//d//object obj = await completedTask;//Console.WriteLine(obj);if (completedTask == task1){int result = await (Task<int>)completedTask;//eConsole.WriteLine(result);}else{string result = await (Task<string>)completedTask;Console.WriteLine(result);}//object result = await (Task<object>)completedTask;//f//Console.WriteLine(result.ToString());


        注意:
        a处:它只是标注了一个会“首先完成”的任务,由于是异步,因此,实际它还没有执行或完成,只是作为以后这个首先完成的一个“引用”。
        b处:真正的执行,同步等待,但不影响主线程,直到任务完成。
        c处:因为返回类型是Task<Task>,需要用Await进行提取内层Task,相当于UnWrap(),与e处类似,也就是说,任务已经执行完成了,现在只是提取内层结果,而不是再执行一次。因为c处的存在,b处实际上是可以取消的,这时候的c处具有两个功能了,执行与提取内层。
        d处:原想进行强制转换,这样后面就可以直接显示结果,但是写法正确,但执行异常,估计内部包裹太多,类型的转换出错的地点难以预料,舍弃。
        e处:因为确定了是对应的具体类型,所以这里转换非常顺序,然后再次用await提取Task<int>内层结果,即int。所以成功。实际上可以直接用task1或task2来显示,但这样为了看看它的转换。
        f处:同样写法正确,但执行异常,原因不明啊。。
        (4)如果输入任务数组中的任何一个任务失败(即抛出了异常),返回的任务也将失败,并且会抛出一个聚合异常,其中包含了所有任务的异常信息。

        (5)你可以使用 await 或 ContinueWith() 等方法来等待返回的任务的完成。当返回的任务完成时,表示其中任意一个输入任务已经完成。修改上面部分:

        Console.WriteLine("前:" + DateTime.Now);Task.WhenAny(task1, task2).ContinueWith(completedTask =>{if (completedTask.Result == task1){int result = ((Task<int>)completedTask.Result).Result;Console.WriteLine(result);}else if (completedTask.Result == task2){string result = ((Task<string>)completedTask.Result).Result;Console.WriteLine(result);}});Console.WriteLine("后:" + DateTime.Now);    

    
        结果:
            前:2023/9/9 17:55:45
            后:2023/9/9 17:55:45
            32        
        说明continuewith只是完成后的下步任务的执行,但并不能改变它是异步执行顺序。

        我们使用 Task.WhenAny() 方法创建了一个任务 firstTask,该任务将在其中任意一个任务完成时变为已完成状态。

        注意,Task.WhenAny() 方法是一个异步方法,需要在异步上下文中使用。在异步方法中使用 await 关键字等待 Task.WhenAny() 方法的完成,或者使用 ContinueWith() 方法注册一个回调函数来处理任务的完成。
    
    
    6、总结:带Wait为同步,带When为异步
        Task 类中的方法可以分为两类:同步方法和异步方法。
        带有 "Wait" 字样的方法是同步方法,会阻塞当前线程,直到任务完成。而带有 "When" 字样的方法是异步方法,返回一个 Task 对象,用于等待多个任务的完成。

        (1)同步方法:
        这些方法会阻塞当前线程,直到任务完成。它们通常以 "Wait" 结尾,如 Task.Wait()、Task.WaitAny() 和 Task.WaitAll()。这些方法会一直等待,直到任务完成或超时。这些方法返回的是 void 或 int 值,用于指示任务是否已经完成。
        
        (2)异步方法:
        这些方法不会阻塞当前线程,而是返回一个 Task 对象,表示异步操作的进行。它们通常以 "When" 开头,如 Task.WhenAny() 和 Task.WhenAll()。这些方法返回的是一个 Task 对象,可以使用 await 关键字等待任务的完成。
        
        注意,虽然 Task.WhenAny() 和 Task.WhenAll() 是异步方法,但它们本身不会执行任何实际的异步操作。它们只是用于等待多个任务中的任意一个或全部任务完成,并返回一个表示完成的 Task 对象。
    
 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS Code 安装方法

1.安装控制台程序.NET SDK 功能&#xff1a;应用能够正常的运行和构建。 .NET SDK下载地址&#xff1a;下载 .NET(Linux、macOS 和 Windows) 2.安装驱动编辑器vscode vscode下载地址&#xff1a;https://code.visualstudio.com/Download 选择System Installer&#xff0c;…

windows mysql8.0主从配置

windows mysql8.0主从配置 一、安装两个MySQL并配置 1. 主库配置my.ini&#xff0c;我的主库是安装版 [mysqld] # 设置mysql的安装目录 basedirD:\\soft\\mysql-5.7.39 # 设置mysql数据库的存放目录 datadirD:\\soft\\mysql-5.7.39\\data #设置3306端口 port3306 #主服务器…

乐趣国学—卧薪尝胆

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

C#企业办公自动化系统asp.net+sqlserver

办公自动化网站是多层次的技术、设备和系统的综合。一个完整的办公自动化网站应包括信息的生成与输入、信息的加工与处理、信息的存储与检索、信息的复制、信息的传输与交流以及信息安全管理等功能。本软件用于构建、整合、扩展和管理企事业机构的整体信息系统&#xff0c;实现…

linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)

一、IO多路复用--epoll实现 1.核心&#xff1a; 红黑树、一张表以及三个接口、 2.实现过程及API 1&#xff09;创建epoll句柄/创建红黑树根节点 int epfdepoll_create(int size--无意义&#xff0c;>0即可)----------成功&#xff1a;返回根节点对应文件描述符&#xf…

Elastic Stack 8.10:更简单的跨集群搜索和身份验证等等

作者&#xff1a;Tyler Perkins, Gilad Gal, Shani Sagiv, George Kobar, Michael Peterson, Aris Papadopoulos Elastic Stack 8.10 增强了跨集群和向量搜索、数据摄取、Kibana 和云注册。 配置远程搜索时获得更大的灵活性&#xff0c;并提供更多信息来分类问题&#xff0c;…

传统生产者和消费者问题,Sychronized版和Lock版

1.生产者和消费者问题Synchronized版 面试&#xff1a;单例模式、排序算法、生产者消费者、死锁 package com.kuang.pc;/*** 线程之间的通信问题&#xff0c;生产者和消费者问题&#xff01; 等待唤醒 &#xff0c;通知唤醒* 线程交替执行 A B 操作同一个变量 num0* A num1;*…

【Vue】入门及生命周期(前后端分离)

目录 一、Vue简介 1、Vue.js是什么 2、库和框架的区别 2.1 库(Library) 2.2 框架(Framework) 3、MVVM的介绍 二、Vue入门 1、Vue快速入门 2、Vue的优势 三、Vue事件 四、Vue生命周期 1、实例 一、Vue简介 1、Vue.js是什么 Vue是一款流行的构建用户界面(UI)的[渐进式…

基于 Alpine 环境构建 aspnetcore6-runtime 的 Docker 镜像

关于 Alpine Linux 此处就不再过多讲述&#xff0c;请自行查看相关文档。 .NET 支持的体系结构 下表列出了当前支持的 .NET 体系结构以及支持它们的 Alpine 版本。 这些版本在 .NET 到达支持终止日期或 Alpine 的体系结构受支持之前仍受支持。请注意&#xff0c;Microsoft 仅正…

postman导入json脚本文件(Collection v1.0、Collection v2.0)

1. 以postman v8.5.1 版本为例 2. 在postman v5.0.2 低版本中导出json脚本文件, 请选择Collection v2.0 Export - Collection v2 3. 在postman v8.5.1 版本 导入 json脚本文件 Import - Collection v2 - Export - Import

InfiniBand vs 光纤通道,存储协议的选择

数字时代&#xff0c;数据量爆发增长&#xff0c;企业越来越迫切地追求高吞吐量、低延迟和更高性能的网络基础设施&#xff0c;存储协议的选择变得愈发至关重要。在众多存储协议中&#xff0c;InfiniBand和光纤通道备受关注。本文旨在深入探讨InfiniBand和光纤通道作为存储协议…

mysql 日志总结

mysql 根据日志的功能&#xff0c;分6种 慢查询日志&#xff1a;记录所有执行时间超过 long_query_time 的所有查询&#xff0c;方便我们对查询进行优化通用查询日志&#xff1a;记录所有连接的起始时间和终止时间&#xff0c;以及连接发送给数据库服务器的所有指令&#xff0…

【Spring面试】二、BeanFactory与IoC容器的加载

文章目录 Q1、BeanFactory的作用是什么&#xff1f;Q2、BeanDefinition的作用是什么&#xff1f;Q3、BeanFactory和ApplicationContext有什么区别&#xff1f;Q4、BeanFactory和FactoryBean有什么区别&#xff1f;Q5、说下Spring IoC容器的加载过程&#xff08;※&#xff09;Q…

【Bun1.0】使用 Bun.js 构建快速、可靠和安全的 JavaScript 应用程序

bun.js Bun 是一个现代的JavaScript运行环境&#xff0c;如Node, Deno。主要特性如下: 启动速度快。更高的性能。完整的工具&#xff08;打包器、转码器、包管理&#xff09;。 官网 https://bun.sh 优点 与传统的 Node.js 不同&#xff0c;Bun.js 提供了一些新的特性和功…

esp32编译问题

-Werroruninitialized 显然变量是初始化了&#xff0c;只是这s13觉等没初始化还居然报错了。 解决方法&#xff1a;add_compile_options(-Wno-uninitialized) 【cmake篇】选择编译器及设置编译参数_cmake选择编译器_仲夏夜之梦~的博客-CSDN博客https://blog.csdn.net/challen…

JavaScript-promise使用+状态

Promise 什么是PromisePromise对象就是异步操作的最终完成和失败的结果&#xff1b; Promise的基本使用&#xff1a; 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compati…

脚本:用python实现五子棋

文章目录 1. 语言2. 效果3. 脚本4. 解读5. FutureReference 1. 语言 Python 无环境配置、无库安装。 2. 效果 以第一回合为例 玩家X 玩家0 3. 脚本 class GomokuGame:def __init__(self, board_size15):self.board_size board_sizeself.board [[ for _ in range(board_…

数字IC设计之时序分析基础概念汇总

1 时钟Clock 理想的时钟模型是一个占空比为50%且周期固定的方波。时钟是FPGA中同步电路逻辑运行的一个基准。理想的时钟信号如下图: 2 时钟抖动Clock Jitter 理想的时钟信号是完美的方波&#xff0c;但是实际的方波是存在一些时钟抖动的。那么什么是时钟抖动呢?时钟抖动&#…

sql注入Less-2

后台sql s q l " S E L E C T ∗ F R O M u s e r s W H E R E i d sql "SELECT * FROM users WHERE id sql"SELECT∗FROMusersWHEREidid LIMIT 0,1"; 注入语句 http://192.168.200.26/Less-3/?id-1? union select 1,2,database();– 使用id-1 便可…

如何在JavaScript中实现字符串模板替换?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用模板字符串&#xff08;Template Strings&#xff09;⭐ 使用正则表达式替换⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门…