1.进程、线程、多线程、计算机概念
【进程】:一个运行程序 【线程】:依托于进程,一个进程可以包含多个线程
【多线程】:多个执行流同时运行,主要分为两种:其一CPU运算太快啦,分时间片——上下文切换(加载环境—计算—保存环境) 微观角度讲,一个核同一时刻只能执行一个线程;宏观的讲是多线程并发。其二,多CPU多核,可以独立工作,4核8线程——核指物理的核,线程指虚拟核
多线程三大特点:不卡主线程、速度快、无序性 【Thread】:C#语言对线程对象的封装
2.同步与异步 【同步】:完成计算之后,再进入下一行,会阻塞 【异步】:不会等待方法的完成,直接进入下一行,不会阻塞
3.不同C#版本线程的进化升级
C#1.0 | Thread | 线程等待、回调、前后台线程,可以扩展Thread封装回调,各种API函数很多,可设置线程优先级,各种API包括:Start(),Suspend(),Resume(),Abort(),Thread.ResetAbort(),Join(), |
C#2.0 | ThreadPool | 线程池使用,设置线程池,等待可用ManualResetEvent,砍掉了很多API,可以设置线程池的最大最小数量 |
C#3.0 | Task |
4.并发编程
并发编程包括【多线程】、【并行处理】、【异步编程】、【响应式编程】
5.Task启动
5.1为什么要有task
Task = Thread + ThreadPool
Thread:容易造成时间+空间开销,而且使用不当,容易造成线程过多,导致时间片切换...
ThreadPool:控制能力比较弱,做thread的延续、阻塞、取消、超市等功能较弱,ThreadPool的控制权在CLR而不在编程者...
Task看起来更像是一个Thread...但是在ThreadPool的基础上进行的封装
.net4.0之后微软极力推荐使用task进行异步运算
引用using System.Threading C#3.0才有、基于ThreadPool
private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1000000000; i++) { lResult += i; } Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
1 Task.Run(() => this.DoSomethingLong("btnTask_Click1")); 2 Task.Run(() => this.DoSomethingLong("btnTask_Click2"));
TaskFactory taskFactory = Task.Factory;//4.0 taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));
new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();
以上三种启动方式并无区别,都是返回一个Task
Run(Action)不具有参数,没有返回值
Task.Run(() => { string name = "无名方法"; Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1000000000; i++) { lResult += i; } Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); });
Console.WriteLine($"这是Task1单击事件Start 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); Task.Run(()=> { Console.WriteLine($"这是Tsak.Run(Action)无名方法Start 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); Random random = new Random(); Thread.Sleep(random.Next(1000,5000)); Console.WriteLine($"这是Tsak.Run(Action)无名方法End 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); }); Console.WriteLine($"这是Task1单击事件End 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
6.多线程阻塞-----Task.WaitAll
设置一场景,睡觉之后要吃饭
6.1不阻塞多线程,就会出现还没睡醒就吃饭的情况
List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(()=> SleepMethod("杨三少"))); listTask.Add(Task.Run(() => SleepMethod("牛大帅"))); listTask.Add(Task.Run(() => SleepMethod("猪宝宝"))); Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
6.2阻塞多线程,使用Task.WaitAll(Task[]),会阻塞当前线程,等着全部任务都完成后,才进入下一行
List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(()=> SleepMethod("杨三少"))); listTask.Add(Task.Run(() => SleepMethod("牛大帅"))); listTask.Add(Task.Run(() => SleepMethod("猪宝宝"))); Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】 Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
6.2.1使用Task.WaitAll如果想不阻塞线程,可以使用Task套用Task
Task.Run(() => {//Task套Task,可以不阻塞 List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(() => DoSomethingLong("杨三少"))); listTask.Add(Task.Run(() => DoSomethingLong("牛大帅"))); listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝"))); Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】 Console.WriteLine($"全部都醒了 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); });
6.3设置最多等待时长,设置最长等待时间,可以看到,杨三少还未睡醒就开吃,谁让杨三少睡的太久了呢,哈哈
List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(()=> SleepMethod("杨三少"))); listTask.Add(Task.Run(() => SleepMethod("牛大帅"))); listTask.Add(Task.Run(() => SleepMethod("猪宝宝"))); //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】 Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待 Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
6.4等待汇总
7.多线程阻塞-----Task.WaitAny
List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(()=> SleepMethod("杨三少"))); listTask.Add(Task.Run(() => SleepMethod("牛大帅"))); listTask.Add(Task.Run(() => SleepMethod("猪宝宝"))); //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】 //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待 Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃” Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
“这次是杨三少醒的最早,所以他就可以先吃辣”
8.多线程不阻塞----Task.WhenAll(Task[]).ContinueWith() Task.WhenAny(Task[]).ContinueWith()
List<Task> listTask = new List<Task>(); listTask.Add(Task.Run(()=> DoSomethingLong("杨三少"))); listTask.Add(Task.Run(() => DoSomethingLong("牛大帅"))); listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝"))); //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】 //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待 //Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃” Task.WhenAny(listTask.ToArray()).ContinueWith(t=> { Console.WriteLine($"第一个已经醒了 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); }); Task.WhenAll(listTask.ToArray()).ContinueWith(t=> { Console.WriteLine($"全部都醒了 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); });
ContinueWith类似于一个回调式的,ContinueWith里面的t就是前面的Task,也就是使用ContinueWith相当于一个回调,当执行了这个Task之后会自动执行ContinueWith里的后续方法。
9.仅用11个线程完成10000个任务
List<int> list = new List<int>(); for (int i = 0; i < 10000; i++) { list.Add(i); } //完成10000个任务 但是只要11个线程 Action<int> action = i => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00")); Thread.Sleep(new Random(i).Next(100, 300)); }; List<Task> taskList = new List<Task>(); foreach (var i in list) { int k = i; taskList.Add(Task.Run(() => action.Invoke(k))); if (taskList.Count > 10) { Task.WaitAny(taskList.ToArray());//只要有一个完成,重新整理集合 taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); } } Task.WhenAll(taskList.ToArray());
10.使用TaskFactory可以知道已完成线程的标识
TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(o => DoSomethingLong("一一一"), "一一一")); taskList.Add(taskFactory.StartNew(o => DoSomethingLong("二二二"), "二二二")); taskList.Add(taskFactory.StartNew(o => DoSomethingLong("三三三"), "三三三")); taskFactory.ContinueWhenAny(taskList.ToArray(), t => { Console.WriteLine($"【{t.AsyncState}】任务已完成 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); taskFactory.ContinueWhenAll(taskList.ToArray(),t=> { Console.WriteLine($"所有任务已完成,第一个是{t[0].AsyncState} 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); });
如果不用TaskFactory,直接用Task,可以通过做个子类的方式来搞。
11.小结
Task.WaitAll(Task[]) | 阻塞,等待Task[]数组里的全部线程任务都完成后方可运行到下一步 |
Task.WaitAny(Task[]) | 阻塞,等待Task[]数组里的某一线程任务完成,方可运行到下一步 |
Task.WhenAll(Task[]).Continue | 不阻塞,等待Task[]数组里的全部线程任务都完成后执行回调 |
Task.WhenAny(Task[]).Continue | 不阻塞,等待Task[]数组里的某一线程任务完成后执行回调 |
12.Thread.Sleep(2000)与Task.Delay(2000)
Thread.Sleep(2000);//等待2s Task.Delay(2000);//延迟2s
12.1Thread.Sleep(2000)
Console.WriteLine($"这是Task1单击事件Start 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Thread.Sleep(2000);//等待 stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine($"这是Task1单击事件End 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
12.2Task.Delay(2000)直接替换Thread.Sleep(2000)
Console.WriteLine($"这是Task1单击事件Start 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task.Delay(2000);//延迟 stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine($"这是Task1单击事件End 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
12.3Task.Delay(2000)的正确用法
Console.WriteLine($"这是Task1单击事件Start 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task.Delay(2000).ContinueWith(t => {//Task.Delay()的返回值仍然是Task, Task.ContinueWith() stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); }); Console.WriteLine($"这是Task1单击事件End 当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
13.多线程中的异常处理
主线程的异常捕捉不到子线程的异常信息。所以新开辟的子线程要写属于自己的try...catch...
子线程如果不阻塞,主线程是捕捉不到。但最最好还是在子线程里书写自己的异常捕捉机制。
Console.WriteLine($"这是主线程单击事件Start 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); List<Task> listTask = new List<Task>(); try { for (int i = 0; i < 5; i++) { Action<string> action = t => { try { Thread.Sleep(100); if (t.Equals("for2")) { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行异常"); throw new Exception($"这是{t},刨出异常"); } if (t.Equals("for3")) { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行异常"); throw new Exception($"这是{t},刨出异常"); } Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行成功"); } catch (Exception ex) { Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 异常信息:{ex.Message}"); } }; string k = $"for{i}"; listTask.Add(Task.Run(() => action.Invoke(k))); } Task.WaitAll(listTask.ToArray()); } catch (Exception ex) { Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 异常信息:{ex.Message}"); } Console.WriteLine($"这是主线程单击事件End 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
14.线程取消
应用场景是多个线程并发执行,某个失败后,希望通知别的线程,都停下来;Task是外部无法中止的,Thread.Abort不要考虑因为不靠谱(原因是线程是OS的资源,无法掌控啥时候取消),在这里需要线程自己停止自己,需要用到公共的访问变量,所有的线程都会不断的去访问它,当某个线程执行异常时修改此公共变量(当然这中间有一定的延迟)。
CancellationTokenSource变量去标志任务是否取消,Cancel()方法用于取消,IsCancellationRequested属性作为每个线程执行的条件;其Token在启动Task的时候传入,如果某一时刻执行Cancel()方法,这个任务会自动放弃,并抛出一个异常。
Console.WriteLine($"这是主线程单击事件Start 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); List<Task> listTask = new List<Task>(); CancellationTokenSource cts = new CancellationTokenSource(); try { for (int i = 0; i < 5; i++) { Action<string> action = t => { try { Thread.Sleep(100); if (t.Equals("for2")) { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行失败"); throw new Exception($"这是{t},刨出异常"); } if (t.Equals("for3")) { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行失败"); throw new Exception($"这是{t},刨出异常"); } if (cts.IsCancellationRequested) { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 放弃执行"); } else { Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 执行成功"); } } catch (Exception ex) { cts.Cancel(); Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 异常信息:{ex.Message}"); } }; string k = $"for{i}"; listTask.Add(Task.Run(() => action.Invoke(k),cts.Token)); } Task.WaitAll(listTask.ToArray()); } catch (Exception ex) { Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 异常信息:{ex.Message}"); } Console.WriteLine($"这是主线程单击事件End 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
15.多线程中的临时变量
15.1直接引用for循环中的i
Console.WriteLine($"这是主线程单击事件Start 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); for (int i = 0; i < 5; i++) { Task.Run(() => { Console.WriteLine($"这是for循环,i={i}"); }); } Console.WriteLine($"这是主线程单击事件End 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
为什么都等于5,线程并没有阻塞,所以5次循环下来之后i就是5啦。
15.2直接引用循环外的局部变量
Console.WriteLine($"这是主线程单击事件Start 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); int k = 0; for (int i = 0; i < 5; i++) { k = i; new Action(() => { Console.WriteLine($"这是for循环,i={k}"); }).BeginInvoke(null, null); } Console.WriteLine($"这是主线程单击事件End 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
为什么都等于4,线程没有阻塞,其实最主要的原因是声明了一次k,全程只有一个k。
15.3在循环内每次声明赋值
Console.WriteLine($"这是主线程单击事件Start 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); for (int i = 0; i < 5; i++) { int k = i; new Action(() => { Console.WriteLine($"这是for循环,i={k}"); }).BeginInvoke(null, null); } Console.WriteLine($"这是主线程单击事件End 当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")} 当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
16.线程安全
共有变量指:都能访问的局部变量、全局变量、数据库中的一个值、磁盘文件等,如果多个线程都去操作一个共有变量,可能会出现意外的结果,这就是多线程并不安全
16.1多线程并不安全
List<Task> listTask = new List<Task>(); int sum = 0; for (int i = 0; i < 10000; i++) { listTask.Add(Task.Run(() => { sum += 1; })); } Task.WhenAll(listTask.ToArray()).ContinueWith((t) => { Console.WriteLine($"最终结果:{sum}"); });
可见结果并不是意料之中的10000.
16.2加lock
//private:定义为私有,防止外界也去lock static:全场唯一 readonly:不要随意赋值 object表示引用类型 private static readonly object objLock = new object(); private void button1_Click(object sender, EventArgs e) { List<Task> listTask = new List<Task>(); int sum = 0; for (int i = 0; i < 10000; i++) { listTask.Add(Task.Run(() => { lock (objLock) { //lock后的方法块,任意时刻只有一个线程可以进入 //只能锁引用类型,相当于占用这个引用链接 //string虽然也是引用类型,但不能用string,因为什么享元模式 sum += 1; } })); } Task.WhenAll(listTask.ToArray()).ContinueWith((t) => { Console.WriteLine($"最终结果:{sum}"); }); }
可见结果是意料之中的10000
16.3有关lock的深思
lock虽然能解决线程安全的问题,同一时刻只能有一个线程可以运行lock后的程序块不并发,但是牺牲了性能,所以有两个建议:
其一、尽可能得缩小lock的范围;其二、尽量不要定义共有变量,可以通过数据拆分来避免冲突
17.await/async
await与async通常成对出现,async放在方法名前(private后面),await放在task对象前。如果只在方法名前加一个async,没有任何意义;如果只在方法内task前面加一个await,会报错。在await task后面的语句,类似于一个回调,方法运行到await task时会自动返回返回主线程继续运行,要等待子线程运行结束后才会继续运行这个回调,并且这个回调的线程是不确定的,可能是主线程,可能是子线程,也可能是其他线程。
不用用void作为async方法的返回类型,如果没有返回值,也要Task,如果有返回值,要返回Task<T>;仅限于编写事件处理程序需要返回void
17.1类似于用一种同步的方法写异步。举例说在主线程中运行了一个task,并且希望在这个子线程task执行之后再运行另一个事件task1,那么就需要task.ContinueWith(task1),在这里task.ContinueWith(task1)就等于await task;task1,其实说白了就是ContinueWith=await。
/* 使用swait/async可以把异步当成同步来使用 * 这个示例演示一个没有返回值的多线程任务,模拟人一天的动作 * 其执行结果的先后顺序是【按钮单击开始】【NoReturn方法Start】【开始吃饭】【吃饭结束】【开始工作】【工作结束】【工作结束】【开始睡觉】【NoReturn方法End】【按钮单击结束】 * 这个示例可能并无实际的意义,但只是用来模拟这种await/async的使用方法 * 不过请注意使用async的方法虽说没有返回值(并没有具体的return task),但其结果返回的是Task */ private async void button1_Click(object sender, EventArgs e) { Console.WriteLine($"ClickEventStart 【按钮单击开始】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); await NoReturn_AwaitAsync(); Console.WriteLine($"ClickEventEnd 【按钮单击结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); } private async static Task NoReturn_AwaitAsync() { Console.WriteLine($"【NoReturn方法Start】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); await Task.Run(()=> { Console.WriteLine($"NoReturn方法await之前 【开始吃饭】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(500); Console.WriteLine($"NoReturn方法await之后 【吃饭结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); await Task.Run(() => { Console.WriteLine($"NoReturn方法await之前 【开始工作】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(500); Console.WriteLine($"NoReturn方法await之后 【工作结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); Console.WriteLine($"NoReturn方法await之后 【开始睡觉】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Console.WriteLine($"【NoReturn方法End】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }
同样的方法从await/async换成Task.ContinueWith来一步步回调则显得代码很冗余
private static void NoReturn_Task_ContinueWith() { Console.WriteLine($"【NoReturn方法Start】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Task.Run(() => { Console.WriteLine($"NoReturn方法await之前 【开始吃饭】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(500); Console.WriteLine($"NoReturn方法await之后 【吃饭结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }).ContinueWith(t=> { Task.Run(() => { Console.WriteLine($"NoReturn方法await之前 【开始工作】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(500); Console.WriteLine($"NoReturn方法await之后 【工作结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }).ContinueWith(t1=> { Console.WriteLine($"NoReturn方法await之后 【开始睡觉】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Console.WriteLine($"【NoReturn方法End】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); }); }
17.2 task有返回值的场景
private void button1_Click(object sender, EventArgs e) { Console.WriteLine($"ClickEventStart 【按钮单击开始】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Task<int> task = Task.Run(() => DoSomething("杨三少")); int result = task.Result;//这一步子线程会阻塞主线程,因为主线程需要调用子线程的执行结果 Console.WriteLine($"子线程执行结果为{result}"); Console.WriteLine($"ClickEventEnd 【按钮单击结束】 当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); } private int DoSomething(string name) { Console.WriteLine($"Task【{name}】 线程ID:{Thread.CurrentThread.ManagedThreadId} 是否在线程池中:{Thread.CurrentThread.IsThreadPoolThread}"); return 22; }