目录
一、Thread、ThreadPool、Task、async/await的关系
三.1、Task task = Task.Run(()=>{})
三.2、Task task = Task.Factory.StartNew(()=>{})
三.3、构造函数:Task task = new Task(()=>{})
3.4.3(扩展,引出async/await)、async Main中await task
五、Thread.Sleep()、Task.Delay()的区别
五.3、示例:Thread.Sleep()在同步任务中的应用
5.5.2、task.GetAwaiter().GetResult()
六、三中创建的Task对象,跟Task.Delay()返回的Task对象的区别
一、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;
}