(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();

        //方式2
        Task.Run(() =>
        {
            Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");
        });

        //方式3
        TaskFactory tf = new TaskFactory();
        tf.StartNew(() =>
        {
            Console.WriteLine($"ID:[{Environment.CurrentManagedThreadId}]线程启动...");
        });

        //方式4
        Task.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()
       {
           // 在异步方法中使用 await
           await 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. **响应性用户界面**:
    在需要保持用户界面的响应性的情况下,可以使用多线程来处理耗时的操作,以避免阻塞用户界面。例如,在进行网络请求、数据库查询或其他耗时的操作时,可以将这些操作放在单独的线程中执行,使用户界面保持流畅。

扫描二维码关注公众号,回复: 16750914 查看本文章

    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);//a
        Console.WriteLine("后:" + DateTime.Now);
        int[] ns = await t;//b
        foreach (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));//f
                await 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);//a
        await firstTask;//b
        Task 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;//e
            Console.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 对象。
    
 

猜你喜欢

转载自blog.csdn.net/dzweather/article/details/132905246