C#中Thread/ThreadPool/Task/async await;Thread.Sleep()/Task.Delay();await task/task.Wait()

目录

一、Thread、ThreadPool、Task、async/await的关系

二、Thread对象的创建方式

三、Task对象的创建方式

三.1、Task task = Task.Run(()=>{})

三.2、Task task = Task.Factory.StartNew(()=>{})

三.3、构造函数:Task task = new Task(()=>{})

三.4、主线程结束时,子线程是否继续进行

3.4.1、控制台应用程序

3.4.2、WinForm/WPF/ASP.NET

3.4.3(扩展,引出async/await)、async Main中await task

3.4.4(扩展,引出Thread)、前台线程

四、async/await

四.1、语法

四.2、执行流程

四.3、await task后的代码,运行在哪个线程上

4.2.1、控制台应用程序

4.2.2、WinForm/WPF/ASP.NET

4.2.3、确保运行在线程池线程

五、Thread.Sleep()、Task.Delay()的区别

五.1、Thread.Sleep()

五.2、Task.Delay()

五.3、示例:Thread.Sleep()在同步任务中的应用

5.3.1、简单示例

5.3.2、WinForm/WPF

5.3.3、循环中的延迟

五.4、示例:Task.Delay()在异步任务中的应用

5.4.1、简单示例

5.4.2、WinForm/WPF

5.4.3、循环中的延迟

5.4.4、定时/间隔性任务

5.4.5、模拟异步API调用

五.5、示例:Task.Delay()在同步任务中的应用

5.5.1、task.Wait()

5.5.2、task.GetAwaiter().GetResult()

5.5.3、同步改异步

六、三中创建的Task对象,跟Task.Delay()返回的Task对象的区别

七、await task、task.Wait()的区别


一、Thread、ThreadPool、Task、async/await的关系

每次创建一个Thread对象,系统都会创建一个线程,去执行委托对应的操作。

Task是基于ThreadPool的,Task对象是抽象的任务,任务会被分配线程执行。

二、Thread对象的创建方式

需要长时间运行的任务

手动创建和销毁线程。

Task.Run()更适合并发任务,而Thread适用于独立运行的长任务

三、Task对象的创建方式

参考:

详细讲解Task.Run(()=>{})和Task.Factory.StartNew(()=>{})的区别,很棒的一篇文章,强烈推荐

CSharp (C#) 中创建Task 的7种方法对比,你用对了吗?_c# task-优快云博客

三.1、Task task = Task.Run(()=>{})

        接收一个委托(Action/Func)或匿名函数(lambda表达式()=>{})作为参数,并将工作项(委托或匿名函数)提交到线程池的任务队列中。线程池(ThreadPool)中的一个空闲线程(后台子线程)会从任务队列中取出任务并执行,而不会阻塞调用Task.Run(()=>{})的线程。

        返回一个Task对象,表示一个(已启动,且在后台子线程上执行的)“后台”任务。

        Task.Run(()=>{})使用的是.NET线程池,它是托管的、动态调整的线程池,会自动回收和管理线程,防止创建过多线程导致性能下降。如果线程池没有可用线程,它会短暂等待,然后可能创建新的线程。任务完成后,线程不会立即销毁,而是返回到线程池等待下一个任务。

三.2、Task task = Task.Factory.StartNew(()=>{})

        同三.1,但可指定更多参数。

        对于短时间的CPU密集型操作,Task.Run(()=>{})默认使用线程池线程。但对于需要长时间运行的任务(例如后台计算),使用参数TaskCreationOptions.LongRunning会让任务运行在一个专门的线程上(可能会创建一个新的专用线程)。

Task.Factory.StartNew(() =>
{
    Console.WriteLine(string.Format("LongRunning_Task:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
}, TaskCreationOptions.LongRunning);

三.3、构造函数:Task task = new Task(()=>{})

        返回一个未启动的Task对象,需要通过task.Start()启动。

        创建子线程吗???

三.4、主线程结束时,子线程是否继续进行

3.4.1、控制台应用程序

        由于Main线程(主线程)退出时,控制台应用程序(进程)会终止,所以未完成的任务(执行在后台子线程上)也可能被终止。

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Task.Run(() =>
        {
            string log1_FilePath = @"C:\Users\Administrator\Desktop\output1.log";
            using (FileStream fs = new FileStream(log1_FilePath, FileMode.OpenOrCreate))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    for (int i = 0; i < 10; i++)
                    {
                        string outputStr1 = string.Format("{0}-For:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString());
                        Console.WriteLine(outputStr1);
                        sw.WriteLine(outputStr1);
                        Task.Delay(500).Wait();//模拟耗时任务
                    }
                }
            } 
        });

        string log2_FilePath = @"C:\Users\Administrator\Desktop\output2.log";
        using (FileStream fs = new FileStream(log2_FilePath, FileMode.OpenOrCreate))
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                string outputStr2 = string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString());
                Console.WriteLine(outputStr2);
                sw.WriteLine(outputStr2);
                //主线程结束,进程可能会终止,Task可能不会执行完
            }
        }

        return;
    }
}

进程很快结束,导致小黑框一闪而过,输出没法截图记录下来,以下是程序执行流程分析。

Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────执行Task.Run(),但不执行其工作项,当前线程仍然是主线程
    ├────输出“Main End:”
    │
    ├────执行Task.Run()的工作项,当前线程从主线程切换到线程池中的一个后台子线程,在后台子线程中执行for循环
    │    ├────输出“-For:”
    │    ├────调用Task.Delay(500).Wait(),让当前线程(后台子线程)休眠500ms,当前线程从后台子线程又切换回主线程
    │
    │
    ├────return结束主线程,进程结束,导致后台子线程也被结束
    │
Main()结束

3.4.2、WinForm/WPF/ASP.NET

        GUI(WinForm/WPF)或Web服务器(ASP.NET)应用程序中,应用程序(进程)通常不会因为Main线程(主线程)退出而终止,所以未完成的任务(执行在后台子线程上)仍然可以继续执行。

3.4.3(扩展,引出async/await)、async Main中await task

        await Task.Run()让Main线程异步等待任务(运行在后台子线程上)完成,避免了进程过早终止。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        await Task.Run(async () =>
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(string.Format("{0}-For:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                await Task.Delay(500);
            }
        });

        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

为了方便分析程序执行流程,将代码拆解并增加输出。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        Task taskMain = Task.Run(async () =>
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(string.Format("{0}-For BeforeDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                Task taskDelay_For = Task.Delay(500);
                Console.WriteLine(string.Format("{0}-For AfterDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                await taskDelay_For;
                Console.WriteLine(string.Format("{0}-For AfterAwait:{1} {2}\n", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            }
        });

        Console.WriteLine(string.Format("Main Mid:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await taskMain;
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────输出“Main Begin:”
    ├────执行Task.Run(),但不执行其工作项,当前线程仍然是主线程
    ├────输出“Main Mid:”
    ├────await taskMain;让当前线程(主线程)异步等待(运行在后台子线程上的)任务完成后,才能执行await taskMain;后的代码。
    │(此时,Main()被挂起,返回到Main()的调用方(即系统)。)
    │(此时,当前线程(主线程)虽然没有被挂起/阻塞,但是没有其他工作可以做了,所以就在await taskMain;这里异步等待着。)
    │
    │
    ├────执行Task.Run()的工作项,当前线程从主线程切换到线程池中的一个后台子线程,在后台子线程中执行for循环
    │    ├────进入第0轮循环,输出“0-For BeforeDelay():”
    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    ├────输出“0-For AfterDelay():”
    │    ├────await taskDelay_For;让当前线程(后台子线程)异步等待计时器500ms的计时结束后,才能执行await taskDelay_For;后的代码。
    │    │(此时,工作项被挂起,返回到工作项的调用方(即系统)。)
    │    │(此时,当前线程(后台子线程)虽然没有被挂起/阻塞,但是没有其他工作可以做了,所以就在await taskDelay_For;这里异步等待着。)
    │    │(此时,主线程、后台子线程都在异步等待:主线程异步等待后台子线程上的任务执行完成、后台子线程异步等待计时器500ms的计时结束。)
    │    │
    │    │
    │    │(此时,后台有一个计时器在计时:taskDelay_For对应的计时器在第500ms时完成计时。)
    │    │(由系统分配控制权,当前线程为后台子线程,恢复工作项)
    │    ├────500ms时,计时器计时结束,taskDelay_For任务完成,执行await taskDelay_For;后的代码,即输出“0-For AfterAwait:”。
    │    │(且当前线程可能切换到线程池中的一个后台子线程上执行await taskDelay_For;后的语句;直到遇到下一个await,才可能再次切换到线程池中的另一个后台子线程上执行该await后的语句。)
    │    │(以上是详细分析第0轮循环的执行流程)
    │    ├────进入第1轮循环,输出“1-For BeforeDelay():”
    │    ├────执行Task.Delay(500)
    │    ├────输出“1-For AfterDelay():”
    │    ├────await taskDelay_For;
    │    │
    │    │
    │    │(以下每一块,都是在同一个后台子线程上执行的)
    │    ├────1000ms时,输出“1-For AfterAwait:”
    │    ├────进入第2轮循环,输出“2-For BeforeDelay():”
    │    ├────执行Task.Delay(500)
    │    ├────输出“2-For AfterDelay():”
    │    ├────await taskDelay_For;
    │    │
    │    │
    │    ├────1500ms时,输出“2-For AfterAwait:”
    │    ├────进入第3轮循环,输出“3-For BeforeDelay():”
    │    ├────执行Task.Delay(500)
    │    ├────输出“3-For AfterDelay():”
    │    ├────await taskDelay_For;
    │    │
    │    │
    │    ├────2000ms时,输出“3-For AfterAwait:”
    │    ├────进入第4轮循环,输出“4-For BeforeDelay():”
    │    ├────执行Task.Delay(500)
    │    ├────输出“4-For AfterDelay():”
    │    ├────await taskDelay_For;
    │    │
    │    │
    │    ├────2500ms时,输出“4-For AfterAwait:”
    ├────退出for循环
    │
    │
    │(for循环退出导致Task.Run()工作项执行完毕,导致(运行在后台子线程上的)taskMain任务执行完毕。)
    ├────异步等待taskMain任务执行完毕了,接着执行await taskMain;后的代码
    │(这里同样可能切换到线程池中的一个后台子线程上执行输出。)
    ├────输出“Main End:”
    │
    │
    ├────return结束主线程,进程结束
    │
Main()结束

3.4.4(扩展,引出Thread)、前台线程

        如果任务运行在线程池之外的子线程上(例如Thread创建的前台线程),那么即使主线程退出,任务仍然会继续运行。

using System;
using System.Threading;
public class Program
{
    public static void Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        Thread thread = new Thread(() =>
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(string.Format("{0}-For:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                Thread.Sleep(500);
            }
        });
        Console.WriteLine(string.Format("Main Mid1:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        thread.IsBackground = false;//设置为前台线程
        Console.WriteLine(string.Format("Main Mid2:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        thread.Start();

        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

调试时可以看出,主线程(执行Main()函数的线程)退出后(执行到return语句了),子线程仍然继续执行。

四、async/await

四.1、语法

async/await主要用于异步任务/异步编程,而不是直接创建线程。

async修饰函数FuncAsync(),表示FuncAsync()是一个异步函数:

1、如果FuncAsync()函数体内有await,表示await后的task会被异步等待执行完毕。

task角度看:这个task,可能在当前线程上执行(比如await Task.Delay(1000)),也可能在线程池的后台子线程上执行(比如await Task.Run(()=>{}))。

程序执行流程角度看:异步等待task执行完毕后才能继续执行(FuncAsync()函数体内的)await task语句之后的语句。因此,在异步等待期间,程序执行流程会从(FuncAsync()函数体内的)await task语句处,返回到FuncAsync()函数的调用处,继续执行FuncAsync()函数调用处之后的语句。

线程角度看:在异步等待期间,FuncAsync()函数调用处之后的语句,和(FuncAsync()函数体内的)await task语句之前的语句,都是由当前线程这同一个线程执行的。异步等待task执行完毕后(FuncAsync()函数体内的)await task语句之后的语句,可能仍然在当前线程上执行,也可能被切换到线程池中的后台子线程上执行,这取决于SynchronizationContext。

2、如果FuncAsync()函数体内没有await,编辑器会警告但不会报错,表示FuncAsync()虽然被async修饰,但它仍然是一个假的异步函数:实际上FuncAsync()函数体内不存在异步等待,那么FuncAsync()函数体内会以同步方式依次执行每一条语句。

async修饰的异步函数FuncAsync(),可返回Task<TResult>/Task/void,返回值表示异步函数FuncAsync()对应的任务。

四.2、执行流程

        循环中的延迟,加入并行执行的代码:如果LoopAsync()是同步的,EncapAsync()就不会执行任何其他任务,而是被卡住3s。但LoopAsync()是async Task方法,支持异步执行。

方案一:void Main()使得,函数Main()执行完毕后,主线程结束,进程(控制台应用程序)结束,导致子线程提前结束,小黑框输出会不完整。

方案二:async void Main()使得,编译器报错【程序不包含适合于入口点的静态"Main"方法】。因为async表示Main()是异步函数,那么系统需要获取Main()返回的Task/Task<TResult>,根据Main()对应的任务的状态来决定:是等待Main()对应的任务执行完毕,再结束主线程、进程;还是不用等待Main()对应的任务执行完毕,直接结束主线程、进程。而Main()返回void的话,系统无法获取Main()对应的任务。

方案三:async Task Main()+await EncapAsync()使得,让Main()成为异步方法:系统等待异步方法Main()执行完毕后,才能结束主线程、进程;而Main()等待异步方法EncapAsync()执行完毕后,才能结束Main()。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    //方案一
    //public static void Main()
    //{
    //    Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    //    Task taskEncap = EncapAsync();//启动异步任务
    //    Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    //    return;
    //}

    //方案二报错:程序不包含适合于入口点的静态"Main"方法
    //public static async void Main()
    //{
    //    Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    //    Task taskEncap = EncapAsync();
    //    Console.WriteLine(string.Format("Main Mid:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    //    await taskEncap;
    //    Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

    //    return;
    //}

    //方案三
    public static async Task Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Task taskEncap = EncapAsync();
        Console.WriteLine(string.Format("Main Mid:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await taskEncap;
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }

    public static async Task EncapAsync()
    {
        Console.WriteLine(string.Format("EncapAsync Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Task taskLoop = LoopAsync();
        Console.WriteLine(string.Format("EncapAsync Mid1:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        //在等待LoopAsync()执行的同时,主线程可以执行其他任务
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(string.Format("{0}-EncapAsync_For BeforeDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            Task taskDelay_Encap = Task.Delay(500);
            Console.WriteLine(string.Format("{0}-EncapAsync_For AfterDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            await taskDelay_Encap;//当前线程也可以异步等待
            Console.WriteLine(string.Format("{0}-EncapAsync_For AfterAwait:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        }

        Console.WriteLine(string.Format("EncapAsync Mid2:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await taskLoop;//确保LoopAsync()对应的异步任务执行完
        Console.WriteLine(string.Format("EncapAsync End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }

    public static async Task LoopAsync()
    {
        Console.WriteLine(string.Format("LoopAsync Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(string.Format("{0}-LoopAsync_For BeforeDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            Task taskDelay_Loop = Task.Delay(1000);
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            await taskDelay_Loop;//非阻塞等待1000ms
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterAwait:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        }

        Console.WriteLine(string.Format("LoopAsync End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

详细分析各个时刻的任务状态:

Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────调用EncapAsync()
    │    ├────调用LoopAsync()
    │    │    ├────执行LoopAsync()函数体内的for循环
    │    │    │    ├────0ms:“0-LoopAsync_For Task.Delay(1000)”计时器/异步任务创建,计时开始时刻
    │    ├────挂起LoopAsync()函数
    │    │
    │    │
    │    ├────执行EncapAsync()函数体内的for循环
    │    │    ├────0+ms:“0-EncapAsync_For Task.Delay(500)”创建
    │    │    │(EncapAsync()先挂起再恢复)
    │    │    ├────500+ms:“0-EncapAsync_For Task.Delay(500)”计时结束时刻
    │    │    │            “1-EncapAsync_For Task.Delay(500)”创建
    ├────挂起EncapAsync()
    │
    │
    │    ├────恢复LoopAsync()
    │    │    ├────恢复LoopAsync()函数体内的for循环
    │    │    │    │(因为0-LoopAsync_For Task.Delay(1000)比0-EncapAsync_For Task.Delay(500)计时器创建要早,所以计时结束时刻:0-LoopAsync_For Task.Delay(1000)在第1000ms,而1-EncapAsync_For Task.Delay(500)在第1000+ms)
    │    │    │    ├────1000ms:“0-LoopAsync_For Task.Delay(1000)”计时结束时刻
    │    │    │    │            “1-LoopAsync_For Task.Delay(1000)”创建
    │    ├────挂起LoopAsync()
    │
    │
    ├────恢复EncapAsync()
    │    ├────恢复EncapAsync()函数体内的for循环
    │    │    ├────1000+ms:“1-EncapAsync_For Task.Delay(500)”计时结束时刻
    │    │    │             “2-EncapAsync_For Task.Delay(500)”创建
    │    │    │(EncapAsync()先挂起再恢复)
    │    │    ├────1500+ms:“2-EncapAsync_For Task.Delay(500)”计时结束时刻
    │    │    │             “3-EncapAsync_For Task.Delay(500)”创建
    ├────挂起EncapAsync()
    │
    │
    │    ├────恢复LoopAsync()
    │    │    ├────恢复LoopAsync()函数体内的for循环
    │    │    │    ├────2000ms:“1-LoopAsync_For Task.Delay(1000)”计时结束时刻
    │    │    │    │            “2-LoopAsync_For Task.Delay(1000)”创建
    │    ├────挂起LoopAsync()
    │
    │
    ├────恢复EncapAsync()
    │    ├────恢复EncapAsync()函数体内的for循环
    │    │    ├────2000+ms:“3-EncapAsync_For Task.Delay(500)”计时结束时刻
    │    │    │             “4-EncapAsync_For Task.Delay(500)”创建
    │    │    │(EncapAsync()先挂起再恢复)
    │    │    ├────2500+ms:“4-EncapAsync_For Task.Delay(500)”计时结束时刻
    │    ├────退出EncapAsync()函数体内的for循环
    │
    │
    │    ├────恢复LoopAsync()
    │    │    ├────恢复LoopAsync()函数体内的for循环
    │    │    │    ├────3000ms:“2-LoopAsync_For Task.Delay(1000)”计时结束时刻
    │    │    ├────退出LoopAsync()函数体内的for循环
    │    ├────退出LoopAsync()
    ├────退出EncapAsync()
    │
    │
    ├────return结束主线程
    │
Main()结束

详细分析程序执行流程:

Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────输出“Main Begin:”
    ├────调用EncapAsync()
    │    ├────输出“EncapAsync Begin:”
    │    ├────调用LoopAsync()
    │    │    ├────输出“LoopAsync Begin:”
    │    │    ├────执行LoopAsync()函数体内的for循环
    │    │    │    ├────进入第0-LoopAsync_For轮循环,输出“0-LoopAsync_For BeforeDelay():”
    │    │    │    ├────执行Task.Delay(1000),计时器在后台开始计时
    │    │    │    ├────输出“0-LoopAsync_For AfterDelay():”
    │    │    │    ├────await taskDelay_Loop;让当前线程(主线程)异步等待计时器1000ms的计时结束后,才能执行await taskDelay_Loop;后的代码。
    │    │    │    │(此时,函数LoopAsync()被挂起,返回到(把控制权交给)LoopAsync()的调用方)
    │    │
    │    │
    │    │(回到调用LoopAsync()的位置,由当前线程(主线程)接着执行此位置后的语句)
    │    ├────输出“EncapAsync Mid1:”
    │    ├────执行EncapAsync()函数体内的for循环
    │    │    ├────进入第0-EncapAsync_For轮循环,输出“0-EncapAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    │    ├────输出“0-EncapAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Encap;让当前线程(主线程)异步等待计时器500ms的计时结束后,才能执行await taskDelay_Encap;后的代码。
    │    │    │(此时,EncapAsync()被挂起,返回到EncapAsync()的调用方)
    │
    │
    │(回到调用EncapAsync()的位置,由当前线程(主线程)接着执行此位置后的语句)
    ├────输出“Main Mid:”
    ├────await taskEncap;让当前线程(主线程)异步等待EncapAsync()对应的任务完成后,才能执行await taskEncap;后的代码。
    │(此时,Main()被挂起,返回到Main()的调用方(即系统)。)
    │(此时,当前线程(主线程)虽然没有被挂起/阻塞,但是没有其他工作可以做了,所以就在await taskEncap;这里异步等待着。)
    │
    │
    │    │    │(此时,后台有两个计时器在计时:第0个taskDelay_Encap对应的计时器在第500+ms时完成计时;第0个taskDelay_Loop对应的计时器在第1000ms时完成计时。)
    │    │    │(由系统分配控制权,当前线程为主线程,恢复EncapAsync())
    │    │    ├────500+ms时,计时器计时结束,taskDelay_Encap任务完成,执行await taskDelay_Encap;后的代码,即输出“0-EncapAsync_For AfterAwait:”。
    │    │    │(且当前线程可能切换到线程池中的一个后台子线程上执行await taskDelay_Encap;后的语句;直到遇到下一个await,才可能再次切换到线程池中的另一个后台子线程上执行该await后的语句。)
    │    │    │(以上是详细分析0-EncapAsync_For循环的执行流程)
    │    │    ├────进入第1-EncapAsync_For轮循环,输出“1-EncapAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    │    ├────输出“1-EncapAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Encap;让当前线程(切换到的后台子线程)异步等待计时器500ms的计时结束后,才能执行await taskDelay_Encap;后的代码。
    │    │    │(此时,EncapAsync()被挂起,返回到EncapAsync()的调用方)
    │
    │
    │    │    │    │(此时,后台有两个计时器在计时:第1个taskDelay_Encap对应的计时器在第1000+ms时完成计时;第0个taskDelay_Loop对应的计时器在第1000ms时完成计时。)
    │    │    │    │(由系统分配控制权,当前线程为切换到的后台子线程,恢复LoopAsync())
    │    │    │    ├────1000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“0-LoopAsync_For AfterAwait:”。
    │    │    │    │(且当前线程可能再次切换到线程池中的一个后台子线程上执行await taskDelay_Loop;后的语句;直到遇到下一个await,才可能再次切换到线程池中的另一个后台子线程上执行该await后的语句。)
    │    │    │    │(以上是详细分析0-LoopAsync_For循环的执行流程)
    │    │    │    ├────进入第1-LoopAsync_For轮循环,输出“1-LoopAsync_For BeforeDelay():”
    │    │    │    ├────执行Task.Delay(1000),计时器在后台开始计时
    │    │    │    ├────输出“1-LoopAsync_For AfterDelay():”
    │    │    │    ├────await taskDelay_Loop;让当前线程(再次切换到的后台子线程)异步等待计时器1000ms的计时结束后,才能执行await taskDelay_Loop;后的代码。
    │    │    │    │(此时,LoopAsync()被挂起,返回到LoopAsync()的调用方)
    │    │    │
    │    │    │
    │    │    │(此时,后台有两个计时器在计时:第1个taskDelay_Encap对应的计时器在第1000+ms时完成计时;第1个taskDelay_Loop对应的计时器在第2000ms时完成计时。)
    │    │    │(由系统分配控制权,当前线程为再次切换到的后台子线程,恢复EncapAsync())
    │    │    │(以下不再详细分析执行流程)
    │    │    ├────1000+ms时,计时器计时结束,taskDelay_Encap任务完成,执行await taskDelay_Encap;后的代码,即输出“1-EncapAsync_For AfterAwait:”。
    │    │    ├────进入第2-EncapAsync_For轮循环,输出“2-EncapAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    │    ├────输出“2-EncapAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Encap;
    │    │    │(此时,EncapAsync()被挂起,返回到EncapAsync()的调用方)
    │    │    │
    │    │    │(由系统分配控制权,恢复EncapAsync())
    │    │    ├────1500+ms后,计时器计时结束,taskDelay_Encap任务完成,执行await taskDelay_Encap;后的代码,即输出“2-EncapAsync_For AfterAwait:”。
    │    │    ├────进入第3-EncapAsync_For轮循环,输出“3-EncapAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    │    ├────输出“3-EncapAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Encap;
    │    │    │(此时,EncapAsync()被挂起,返回到EncapAsync()的调用方)
    │
    │
    │    │    │    │(由系统分配控制权,恢复LoopAsync())
    │    │    │    ├────2000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“1-LoopAsync_For AfterAwait:”。
    │    │    │    ├────进入第2-LoopAsync_For轮循环,输出“2-LoopAsync_For BeforeDelay():”
    │    │    │    ├────执行Task.Delay(1000),计时器在后台开始计时
    │    │    │    ├────输出“2-LoopAsync_For AfterDelay():”
    │    │    │    ├────await taskDelay_Loop;
    │    │    │    │(此时,LoopAsync()被挂起,返回到LoopAsync()的调用方)
    │
    │
    │    │    │(由系统分配控制权,恢复EncapAsync())
    │    │    ├────2000+ms时,计时器计时结束,taskDelay_Encap任务完成,执行await taskDelay_Encap;后的代码,即输出“3-EncapAsync_For AfterAwait:”。
    │    │    ├────进入第4-EncapAsync_For轮循环,输出“4-EncapAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(500),计时器在后台开始计时
    │    │    ├────输出“4-EncapAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Encap;
    │    │    │(此时,EncapAsync()被挂起,返回到EncapAsync()的调用方)
    │    │    │
    │    │    │(由系统分配控制权,恢复EncapAsync())
    │    │    ├────2500+ms后,计时器计时结束,taskDelay_Encap任务完成,执行await taskDelay_Encap;后的代码,即输出“4-EncapAsync_For AfterAwait:”。
    │    ├────退出EncapAsync()函数体内的for循环
    │    ├────输出“EncapAsync Mid2:”
    │    ├────await taskLoop;让当前线程异步等待LoopAsync()对应的任务完成后,才能执行await taskLoop;后的代码。
    │
    │
    │    │    │    │(由系统分配控制权,恢复LoopAsync())
    │    │    │    ├────3000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“2-LoopAsync_For AfterAwait:”。
    │    │    ├────退出LoopAsync()函数体内的for循环
    │    │    ├────输出“LoopAsync End:”
    │    ├────退出LoopAsync()
    │    ├────异步等待taskLoop任务执行完毕了,接着执行await taskLoop;后的代码
    │    ├────输出“EncapAsync End:”
    ├────退出EncapAsync()
    ├────异步等待taskEncap任务执行完毕了,接着执行await taskEncap;后的代码
    ├────输出“Main End:”
    │
    │
    ├────return结束主线程,进程结束
    │
Main()结束

代码可以同时执行多个任务(我觉得,指的是函数???),而不是按顺序阻塞执行。

四.3、await task后的代码,运行在哪个线程上

取决于SynchronizationContext

4.3.1、控制台应用程序

        在控制台应用程序中,没有SynchronizationContext,所以await Task.Delay(1000)之后的代码,可能在Main线程上运行,也可能被调度到线程池中的一个后台子线程上运行。

4.3.2、WinForm/WPF/ASP.NET

        在WinForm/WPF/ASP.NET应用程序中,SynchronizationContext通常会确保任务回到原来的线程上运行。

4.3.3、确保运行在线程池线程

        Task.Run(()=>{}),或者显示切换到线程池:可以让【整个代码】不在Main线程,而是在ThreadPool线程运行。

public static async Task LoopAsync()
{
    //显示切换到线程池
    await Task.Run(() => Console.WriteLine(string.Format("LoopAsync Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString())));

    for (int i = 0; i < 3; i++)
    {
	    Console.WriteLine(string.Format("{0}-LoopAsync_For:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()))
        await Task.Delay(1000);
    }
	
	return;
}

        使【await之后的代码】不会切换回Main线程,而是继续在ThreadPool线程运行。

await Task.Delay(1000).ConfigureAwait(false);

五、Thread.Sleep()、Task.Delay()的区别

五.1、Thread.Sleep()

        Thread.Sleep()会挂起/阻塞当前线程,且不切换线程:当前线程仍然占用CPU,但处于休眠状态,这导致CPU资源浪费,无法执行其他任务。如果当前线程是UI线程,则UI冻结。

        一旦调用Thread.Sleep(),必须等到时间结束。

五.2、Task.Delay()

        创建一个异步的延迟任务,并立即返回Task对象,不会阻塞当前线程,且不创建新的线程:会让当前线程执行其他任务,不会白白消耗CPU资源。如果当前线程是UI线程,可以避免UI线程冻结。

        Task.Delay()的内部实现依赖于TaskCompletionSource+Timer(计时器),利用已有的计时器机制。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async void Main()
    {
        Task taskInvoke = InvokeAsync();
        await taskInvoke;

        return;
    }

    public static async Task InvokeAsync()
    {
        Task taskDelay = Task.Delay(1000);
        await taskDelay;

        return;
    }

    public static Task Delay(int milliSeconds)
    {
        var tcs = new TaskCompletionSource<bool>();

        var timer = new Timer(_ = >
        {
            tcs.SetResult(true);//nms后,标记Task完成
        }, null, milliSeconds, Timeout.Infinite);

        return tcs.Task;//返回未完成的Task,调用者会await它
    }
}
Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────调用InvokeAsync()
    │    ├────调用Task.Delay()
    │    │    ├────创建TaskCompletionSource,生成一个未完成的任务。
    │    │    ├────创建Timer,开始计时器异步等待。
    │    │    │(Timer利用操作系统的定时机制进行计时,在后台启动计时并等待nms后触发计时器的回调,因此Timer不会占用任何线程。)
    │    │    │(创建TaskCompletionSource、创建Timer,都是在调用函数Task.Delay()的当前线程上执行的,并不会阻塞当前线程,且并不会使用线程池中的后台子线程。)
    │    │    ├────立即返回task(还未完成)。
    │    │
    │    │
    │    ├────await遇见Task对象时,会告诉编译器:让出当前线程的控制权(给await所在函数的调用方)。
    │    │(允许当前线程执行其他任务,并不会阻塞当前线程,同时后台计时器仍在计时。)
    │    │
    │    │
    │    │    ├────计时器结束时,计时器的回调tcs.SetResult(true);会标记task为完成,这是在线程池中的一个后台子线程上执行的。
    │    ├────然后恢复执行await之后的代码。
    │    │(await之后的代码是否继续在原来的线程上运行,取决于SynchronizationContext。而且,就算在ThreadPool上执行,也不能保证跟【执行计时器的回调时的线程】总是同一个线程。)
    │
    │
Main()结束

可以通过CancellationToken终止等待。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        var cts = new CancellationTokenSource();
        var taskDelay = Task.Delay(5000, cts.Token);
        cts.CancelAfter(2000);//2秒后Task.Delay()被取消,不会继续等待慢5秒。

        try
        {
            await taskDelay;
        }
        catch (TaskCanceledException ex)
        {
            Console.WriteLine("Task was canceled.");
        }

        return;
    }
}

五.3、示例:Thread.Sleep()在同步任务中的应用

Thread.Sleep()适合同步场景。

5.3.1、简单示例

using System;
using System.Threading;

public class Program
{
    public static void Main()
    {
        Console.WriteLine(string.Format("Start:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Thread.Sleep(3000);//阻塞主线程3秒
        Console.WriteLine(string.Format("End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

5.3.2、WinForm/WPF

private async void Button_Click(object sender, EventArgs e)
{
    button.Text = "Waiting...";
    Thread.Sleep(3000);//会卡住UI线程3s,导致界面无响应,用户无法点击其他按钮
    button.Text = "Done";

    return;
}

5.3.3、循环中的延迟

using System;
using System.Threading;
using System.Threading.Tasks;
 
public class Program
{
    public static async Task Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        var taskLoop = LoopAsync();
        Console.WriteLine(string.Format("Main Mid1:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
 
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(string.Format("{0}-Main_For BeforeSleep():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            Thread.Sleep(500);//让当前线程阻塞500ms,不能执行其他任务
            Console.WriteLine(string.Format("{0}-Main_For AfterSleep():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        }

        Console.WriteLine(string.Format("Main Mid2:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await taskLoop;
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
 
    public static async Task LoopAsync()
    {
        Console.WriteLine(string.Format("LoopAsync Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
 
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(string.Format("{0}-LoopAsync_For BeforeDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            Task taskDelay_Loop = Task.Delay(1000);
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            await taskDelay_Loop;
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterAwait:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        }

        Console.WriteLine(string.Format("LoopAsync End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

Main()开始,当前线程是主线程,即执行Main()函数的线程
    │
    ├────输出“Main Begin:”
    ├────调用LoopAsync()
    │    ├────输出“LoopAsync Begin:”
    │    ├────执行LoopAsync()函数体内的for循环
    │    │    ├────进入第0-LoopAsync_For轮循环,输出“0-LoopAsync_For BeforeDelay():”
    │    │    ├────执行Task.Delay(1000),计时器在后台开始计时,并返回Task对象给当前线程
    │    │    ├────输出“0-LoopAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Loop;操作是异步的
    │    │    │(此时,函数LoopAsync()被挂起(暂停1000ms),返回到(即,不会阻塞当前线程(主线程),会让出线程控制权给Main())LoopAsync()的调用方。)
    │    │    │(当await taskDelay_Loop;完成时,函数LoopAsync()对应的任务恢复,await taskDelay_Loop;后续语句的执行可能仍然在主线程上运行,也可能切换到线程池中的后台子线程,这个行为取决于SynchronizationContext和调度策略。)
    │
    │
    ├────输出“Main Mid1:”
    ├────执行Main()函数体内的for循环
    │    ├────进入第0-Main_For轮循环,输出“0-Main_For BeforeSleep():”
    │    ├────Thread.Sleep(500);操作是同步的
    │    │(此时,当前线程(主线程)阻塞500ms,无法执行其他操作,直到Thread.Sleep()结束。但这个阻塞不会影响LoopAsync()的延迟计时,因为Task.Delay()早已开始计时。)
    │
    │
    │    │(当前线程(主线程)恢复Main()函数体内的for循环)
    │    ├────500+ms时,Thread.Sleep(500)结束,输出“0-Main_For AfterSleep():”
    │    │(以上是详细分析0-Main_For循环的执行流程)
    │    ├────进入第1-Main_For轮循环,输出“1-Main_For BeforeSleep():”
    │    ├────Thread.Sleep(500);
    │
    │
    │    │    │(当前线程为主线程,恢复LoopAsync(),还是异步执行)
    │    │    ├────1000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“0-LoopAsync_For AfterAwait:”
    │    │    │(且当前线程可能切换到线程池中的一个后台子线程上执行await taskDelay_Loop;后的语句;直到遇到下一个await,才可能再次切换到线程池中的另一个后台子线程上执行该await后的语句。)
    │    │    │(以上是详细分析0-LoopAsync_For循环的执行流程)
    │    │    ├────进入第1-LoopAsync_For轮循环,输出“1-LoopAsync_For BeforeDelay():”
    │    │    ├────Task.Delay(1000);
    │    │    ├────输出“1-LoopAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Loop;
    │    │    │(此时,函数LoopAsync()被挂起,返回到LoopAsync()的调用方。)
    │    │    │(此时,主线程因为Thread.Sleep()被阻塞,失去了并行执行的能力。)
    │
    │
    │    ├────1000+ms时,Thread.Sleep(500)结束,输出“1-Main_For AfterSleep():”
    │    ├────进入第2-Main_For轮循环,输出“2-Main_For BeforeSleep():”
    │    ├────Thread.Sleep(500);
    │    ├────1500+ms时,Thread.Sleep(500)结束,输出“2-Main_For AfterSleep():”
    │    ├────进入第3-Main_For轮循环,输出“3-Main_For BeforeSleep():”
    │    ├────Thread.Sleep(500);
    │
    │
    │    │    │(恢复LoopAsync())
    │    │    ├────2000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“1-LoopAsync_For AfterAwait:”
    │    │    ├────进入第2-LoopAsync_For轮循环,输出“2-LoopAsync_For BeforeDelay():”
    │    │    ├────Task.Delay(1000);
    │    │    ├────输出“2-LoopAsync_For AfterDelay():”
    │    │    ├────await taskDelay_Loop;
    │    │    │(挂起LoopAsync())
    │
    │
    │    ├────2000+ms时,Thread.Sleep(500)结束,输出“3-Main_For AfterSleep():”
    │    ├────进入第4-Main_For轮循环,输出“4-Main_For BeforeSleep():”
    │    ├────Thread.Sleep(500);
    │    ├────2500+ms时,Thread.Sleep(500)结束,输出“4-Main_For AfterSleep():”
    ├────退出Main()函数体内的for循环
    ├────输出“Main Mid2:”
    ├────await taskLoop;让当前线程等待LoopAsync()对应的任务异步执行完毕后,才能执行await taskLoop;后的语句
    │
    │
    │    │    │(恢复LoopAsync())
    │    │    ├────3000ms时,计时器计时结束,taskDelay_Loop任务完成,执行await taskDelay_Loop;后的代码,即输出“2-LoopAsync_For AfterAwait:”
    │    ├────退出LoopAsync()函数体内的for循环
    │    ├────输出“LoopAsync End:”
    ├────退出LoopAsync()
    ├────taskLoop任务异步执行完毕了,接着执行await taskLoop;后的代码
    ├────输出“Main End:”
    │
    │
    ├────return结束主线程,进程结束
    │
Main()结束

LoopAsync()的第一次输出,一定在Main()的第一次输出之前吗???

await后的内容,包括Main中的内容,都可能切换到子线程中执行吗???

五.4、示例:Task.Delay()在异步任务中的应用

Task.Delay()适合异步场景(async/await)、同步场景(Task.Run())。

5.4.1、简单示例

using System;
using System.Threading;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await Task.Delay(3000);
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

5.4.2、WinForm/WPF

private async void Button_Click(object sender, EventArgs e)
{
    button.Text = "Waiting...";
    await Task.Delay(3000); //UI仍然可以响应,避免UI冻结
    button.Text = "Done";

    return;
}

5.4.3、循环中的延迟

        Task.Delay(1000)返回一个task,表示一个(非阻塞)延迟1s后才能继续执行后续语句的任务。await task关键字告诉编译器等待这个task完成(即延迟),但不会阻塞当前线程,让当前线程(回到await task所在函数的调用方)继续处理其他任务,而不是一直等待1s。

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Task taskLoop = LoopAsync();
        Console.WriteLine(string.Format("Main Mid:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        await taskLoop;
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }

    public static async Task LoopAsync()
    {
        Console.WriteLine(string.Format("LoopAsync Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(string.Format("{0}-LoopAsync_For BeforeDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            Task taskDelay_Loop = Task.Delay(1000);//每次迭代等待1秒(非阻塞)
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterDelay():{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
            await taskDelay_Loop;
            Console.WriteLine(string.Format("{0}-LoopAsync_For AfterAwait:{1} {2}", i, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        }

        Console.WriteLine(string.Format("LoopAsync End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

5.4.4、定时/间隔性任务

如数据轮询/自动刷新功能。

while (true)
{
    Console.WriteLine("Doing work...");
    await Task.Delay(5000);//每5s执行一次,不占用线程
}

5.4.5、模拟异步API调用

public static async Task<string> FetchDataAsync()
{
    await Task.Delay(1000);//模拟网络请求延迟

    return "Data fetched!";
}

五.5、示例:Task.Delay()在同步任务中的应用

        Task.Delay()本质是异步的,直接在同步代码(指的是函数/方法前无async关键字)中使用时,需要用.Wait()或.GetAwaiter().GetResult()来同步等待。

        task.Wait()会捕获并包装异常,难以调试。task.GetAwaiter().GetResult()直接抛出原始异常,更易调试。

5.5.1、task.Wait()

        Task.Delay(1000).Wait()会阻塞当前线程,直到Task.Delay()完成,实际上相当于Thread.Sleep(1000)。

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Task.Delay(1000).Wait();//在同步代码中等待1秒
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

5.5.2、task.GetAwaiter().GetResult()

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Task.Delay(1000).GetAwaiter().GetResult();//等待1秒
        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        return;
    }
}

5.5.3、同步改异步

        在同步代码里(用.Wait()、.GetAwaiter().GetResult())强制等待Task.Delay()时,在UI线程中使用时,会导致UI卡死。但是可以结合Task.Run()异步执行这个同步代码。

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Console.WriteLine(string.Format("Main Begin:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));

        //方案一
        //Task.Run(async () =>
        //{
        //    await Task.Delay(1000);
        //    Console.WriteLine(string.Format("[await Task.Delay()] in Task.Run():{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        //});

        方案二
        Task.Run(() =>
        {//Task.Run()启动一个新的后台任务,执行Task.Delay(1000)
            Task.Delay(1000).Wait();
            Console.WriteLine(string.Format("[Task.Delay().Wait()] in Task.Run():{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        });

        //方案三
        //Task.Run(() =>
        //{
        //    Thread.Sleep(1000);
        //    Console.WriteLine(string.Format("[Thread.Sleep()] in Task.Run():{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        //});

        Console.WriteLine(string.Format("Main End:{0} {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        Console.ReadLine();//防止程序立即退出

        return;
    }
}

Main线程不会被阻塞???

Thread.Sleep()会放到async/await中吗???

六、三中创建的Task对象,跟Task.Delay()返回的Task对象的区别

Task.Run(()=>{})跟Task.Run(async ()=>{await Task.Delay(1000)})的区别

三中创建的Task对象task,代表一个工作项执行在子线程上的任务。Task.Delay()返回的Task对象task,代表一个延迟任务,利用已有的Timer计时器(操作系统的定时机制)在后台进行计时,不占用任何线程。

七、await task、task.Wait()的区别

task可以被await,也可以被.Wait()

await task会暂停await task所在的异步代码/函数/方法,返回到【await task所在函数的调用者】;不会阻塞当前线程(也不会创建新线程),会让出线程控制权给【await task所在函数的调用者】,调用者(的后续操作)继续执行在当前线程上。

当前线程不会卡在await task处等task执行完,也不会立即执行【await task之后的代码】。而是等task完成后(可能是三中创建的task由子线程运行,也可能是Task.Delay()返回的task由Timer在后台计时),才会在当前线程或后台子线程(取决于SynchronizationContext)上执行【await task之后的代码】。

可以把【await task之后的代码】想象成回调,它不会立即执行,而是注册到task的完成事件上。

等价于:

MyMethodAsync().ContinueWith(t => Console.WriteLine("Codes after await Task.Delay()"));

task.Wait()相当于五.5中所述,把异步任务的异步等待(await task)/不等待(不处理task)变成同步等待(task.Wait())。

Main() 开始
  ├── 调用 MyAsyncMethod()
  │    ├── 执行 "Before await Task.Delay"
  │    ├── 遇到 await,挂起 MyAsyncMethod()
  │
  ├── 执行 "Main continues after calling MyAsyncMethod"
  ├── 遇到 await task,挂起 Main()
  │
  ├── (1秒后...) 继续 MyAsyncMethod() 的 "After await Task.Delay"
  ├── MyAsyncMethod() 结束
  ├── 继续执行 "Main End"

【代码需要验证???】

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class Program
    {
        private static readonly Stopwatch mWatch = new Stopwatch();
        private static const string mUrl = "http://www.cnblogs.com/";
        private static const string mPath = @"C:\Users\Administrator\Desktop\log.txt";

        public static void Main(string[] args)
        {
            mWatch.Start();

            //Task taskEncap = EncapAsync(1);
            //taskEncap.Wait();

            //EncapAsync(1);

            //Task<int> taskWeb = WebAsync(1);
            //taskWeb.Wait();

            Console.Read();

            return;
        }

        //private static Task EncapAsync(int id)
        //private static async Task EncapAsync(int id)
        private static async void EncapAsync(int id)
        {
            //Task<int> taskWeb = WebAsync(id);
            //taskWeb.Wait();

            //Task<int> taskWeb = WebAsync(id);
            //await taskWeb;

            //await WebAsync(id);

            //WebAsync(id);

            //return Task.CompletedTask;
        }

        private static async Task<int> WebAsync(int id)
        {
            WebClient wc = new WebClient();
            string result = await wc.DownloadStringTaskAsync(address);

            return result.Length;
        }
    }
public static string PostSync(string url, List<KeyValuePair<string, string>> paramsList, Dictionary<string, string> header = null)
{
    HttpClient client = new HttpClient(new HttpClientHandler() { UseCookies = false });
    HttpContent content = new FormUrlEncodedContent(paramsList);
    HttpResponseMessage response = client.PostAsync(url, content).Result;
    response.EnsureSuccessStatusCode();

    return response.Content.ReadAsStringAsync().Result;
}

public static async Task<string> PostAsync(string url, List<KeyValuePair<string, string>> paramsList, Dictionary<string, string> header = null)
{
    HttpClient client = new HttpClient(new HttpClientHandler() { UseCookies = false });
    HttpContent content = new FormUrlEncodedContent(paramsList);
    HttpResponseMessage response = await client.PostAsync(url, content);
    response.EnsureSuccessStatusCode();

    string responseBody = await response.Content.ReadAsStringAsync();

    return responseBody;
}

八、多线程编程、异步编程的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值