专业设计服务网站,智能化建设网站,东莞网站建设推广费用,wordpress能放视频教程C#高级–多线程详解
零、文章目录
一、什么是多线程
1、进程
当一个程序开始运行时#xff0c;它就是一个进程#xff0c;进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。
2、线程
线程是程序中的一个执行流#xff0c;每个…C#高级–多线程详解
零、文章目录
一、什么是多线程
1、进程
当一个程序开始运行时它就是一个进程进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。
2、线程
线程是程序中的一个执行流每个线程都有自己的专有寄存器(栈指针、程序计数器等)但代码区是共享的即不同的线程可以执行同样的函数。
3、句柄
句柄是Windows系统中对象或实例的标识这些对象包括模块、应用程序实例、窗口、控制、位图、GDI对象、资源、文件等。
4、多线程
1多线程概念
多线程是指程序中包含多个执行流即在一个程序中可以同时运行多个不同的线程来执行不同的任务也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
2多线程优点
可以提高CPU的利用率。在多线程程序中一个线程必须等待的时候CPU可以运行其它的线程而不是等待这样就大大提高了程序的效率。牺牲空间计算资源来换取时间
3多线程缺点
线程也是程序所以线程运行需要占用计算机资源线程越多占用资源也越多。占内存多多线程需要协调和管理所以需要CPU跟踪线程消耗CPU资源。占cpu多线程之间对共享资源的访问会相互影响必须解决竞用共享资源的问题。多线程存在资源共享问题线程太多会导致控制太复杂最终可能造成很多Bug。管理麻烦容易产生bug
4为什么计算机可以多线程
程序运行需要计算机资源操作系统就会去申请CPU来处理CPU在执行动作的时候是分片执行的。分片把CPU的处理能力进行切分操作系统在调度的时候按照切片去处理不同的计算需求按照规则分配切片计算资源对于同一个计算机核心来讲所有的运行都是串行的但是因为分片的存在感觉几个程序同时在向前推进。
5何时建议使用多线程
当主线程试图执行冗长的操作但系统会卡界面体验非常不好这时候可以开辟一个新线程来处理这项冗长的工作。当请求别的数据库服务器、业务服务器等可以开辟一个新线程让主线程继续干别的事。利用多线程拆分复杂运算提高计算速度。
6何时不建议使用多线程
当单线程能很好解决就不要为了使用多线程而用多线程。
5、同步异步
1同步方法
线性执行从上往下依次执行同步方法执行慢消耗的计算机资源少。
2异步方法
线程和线程之间不再线型执行多个线程总的耗时少执行快消耗的计算机资源多各线程执行是无序的。
6、C#中的多线程
Thread/ThreadPool/Task 都是C#语言在操作计算机的资源时封装的帮助类库。
二、Thread
Thread是.Net最早的多线程处理方式它出现在.Net1.0时代虽然现在已逐渐被微软所抛弃微软强烈推荐使用Task但从多线程完整性的角度上来说我们有必要了解下早期多线程的是怎么处理的以便体会.Net体系中多线程处理方式的进化。
1、如何开启新线程 分析Thread类的源码发现其构造函数参数有两类 ThreadStart类是无参无返回值的委托。ParameterizedThreadStart类是有一个object类型参数但无返回值的委托。 开启了一个新的线程1 ParameterizedThreadStart parameterizedThreadStart new ParameterizedThreadStart((oInstacnce)
{Debug.WriteLine($ParameterizedThreadStart--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString( HH:mm:ss.fff)});
});
Thread thread new Thread(parameterizedThreadStart);
thread.Start();开启了一个新的线程2 ThreadStart threadStart new ThreadStart(()
{this.DoSomething(张三);
});
Thread thread new Thread(threadStart);
thread.Start();2、线程的停止等待
Thread thread new Thread(()
{this.DoSomething(张三, 5000);
});
thread.Start();
//thread.Suspend(); //表示线程暂停现在已弃用NetCore平台已经不支持
//thread.Resume(); //线程恢复执行弃用NetCore平台已经不支持
//thread.Abort(); //线程停止子线程对外抛出了一个异常线程是无法从外部去终止的
//Thread.ResetAbort();//停止的线程继续去执行//thread.ThreadState
//根据线程状态ThreadState判断实现线程间歇性休息
//while (thread.ThreadState ! System.Threading.ThreadState.Stopped)
//{
// Thread.Sleep(500); //当前休息500ms不消耗计算机资源的
//}thread.Join();//主线程等待直到当前线程执行完毕
//thread.Join(500);//主线程等待500毫秒不管当前线程执行是否完毕都继续往后执行
//thread.Join(new TimeSpan(500*10000));//主线程等待500毫秒不管当前线程执行是否完毕都继续往后执行
//TimeSpan 单位100纳秒 1毫秒10000*100纳秒3、后台线程前台线程
1后台线程界面关闭线程也就随之消失
2前台线程界面关闭线程会等待执行完才结束
Thread thread new Thread(()
{this.DoSomething(张三);
});
thread.Start();
thread.IsBackground true;//后台线程界面关闭线程也就随之消失
thread.IsBackground false;//前台线程界面关闭线程会等待执行完才结束
thread.Start();4、跨线程操作主线程UI
Thread thread new Thread(()
{for (int i 0; i 5; i){Thread.Sleep(500);textBox1.Invoke(new Action(() textBox1.Text i.ToString()));}
});
thread.Start();5、线程的优先级
设置优先级只是提高了他被优先执行的概率
Thread thread new Thread(()
{this.DoSomething(张三);
});
// 线程的优先级最高
thread.Priority ThreadPriority.Highest;Thread thread1 new Thread(()
{this.DoSomething(张三);
});
// 线程的优先级最低
thread1.Priority ThreadPriority.Lowest;
thread.Start();
thread1.Start();//线程开启后根据优先级来执行6、扩展封装
1实现两个委托多线程顺序执行 方法封装 private void CallBackThread(Action action1, Action action2)
{Thread thread new Thread(() {action1.Invoke();action2.Invoke();});thread.Start();
}方法调用 Action action1 ()
{this.DoSomething(张三);
};
Action action2 ()
{this.DoSomething(李四);
};
//不会阻塞线程
CallBackThread(action1, action2);运行结果 DoSomething--Start--张三--06--16:57:21.411
DoSomething--End--张三--06--16:57:23.412
DoSomething--Start--李四--06--16:57:23.417
DoSomething--End--李四--06--16:57:25.4232实现获取多线程执行委托的结果 方法封装 private FuncT CallBackFuncT(FuncT func)
{T t default(T);ThreadStart threadStart new ThreadStart(() {t func.Invoke();});Thread thread new Thread(threadStart);thread.Start();return new FuncT(() {thread.Join();//等待thread执行完成return t;});
}方法调用 Funcint func ()
{this.DoSomething(王五);return DateTime.Now.Year;
};
//这一步不会阻塞界面
Funcint func1 this.CallBackFuncint(func);
Debug.WriteLine(线程开启后计算结果出来前);
//这里会等待线程计算出结果才继续往后执行
int iResult func1.Invoke();
Debug.WriteLine($计算结果{iResult});运行结果 线程开启后计算结果出来前
DoSomething--Start--王五--09--17:06:12.088
DoSomething--End--王五--09--17:06:14.090
计算结果20217、数据槽
为了解决多线程竞用共享资源的问题引入数据槽的概念即将数据存放到线程的环境块中使该数据只能单一线程访问。
1AllocateNamedDataSlot命名槽位和AllocateDataSlot未命名槽位
在主线程上设置槽位使该数据只能被主线程读取其它线程无法访问 AllocateNamedDataSlot命名槽位 var d Thread.AllocateNamedDataSlot(userName);
Thread.SetData(d, 张三);
//声明一个子线程
var t1 new Thread(()
{Debug.WriteLine($子线程中读取数据{Thread.GetData(d)});
});
t1.Start();
//主线程中读取数据
Debug.WriteLine($主线程中读取数据{Thread.GetData(d)});AllocateDataSlot未命名槽位 var d Thread.AllocateDataSlot();
Thread.SetData(d, 李四);
//声明一个子线程
var t1 new Thread(()
{Debug.WriteLine($子线程中读取数据{Thread.GetData(d)});
});
t1.Start();
//主线程中读取数据
Debug.WriteLine($主线程中读取数据{Thread.GetData(d)});运行结果 主线程中读取数据张三
子线程中读取数据
主线程中读取数据李四
子线程中读取数据2利用特性[ThreadStatic]
在主线程中给ThreadStatic特性标注的变量赋值则只有主线程能访问该变量 变量标记特性 [ThreadStatic]
private static string Age string.Empty;线程访问变量 Age 小女子年方28;
//声明一个子线程
var t1 new Thread(()
{Debug.WriteLine($子线程中读取数据{Age});
});
t1.Start();
//主线程中读取数据
Debug.WriteLine($主线程中读取数据{Age});运行结果 主线程中读取数据小女子年方28
子线程中读取数据3利用ThreadLocal线程的本地存储
在主线程中声明ThreadLocal变量并对其赋值则只有主线程能访问该变量 程序访问 ThreadLocalstring tlocalSex new ThreadLocalstring();
tlocalSex.Value 女博士;
//声明一个子线程
var t1 new Thread(()
{Debug.WriteLine($子线程中读取数据{tlocalSex.Value});
});
t1.Start();
//主线程中读取数据
Debug.WriteLine($主线程中读取数据{tlocalSex.Value});运行结果 主线程中读取数据女博士
子线程中读取数据8、内存栅栏
当多个线程共享一个变量的时候在Release模式的优化下子线程会将共享变量加载到cup Cache中导致主线程不能使用该变量而无法运行
1默认情况(Release模式主线程不能正常运行)
var isStop false;
var t new Thread(()
{var isSuccess false;while (!isStop){isSuccess !isSuccess;}Trace.WriteLine(子线程执行成功);
});
t.Start();
Thread.Sleep(1000);
isStop true;
t.Join();
Trace.WriteLine(主线程执行结束);2MemoryBarrier解决共享变量Release模式下可以正常运行
在此方法之前的内存写入都要及时从cpu cache中更新到memory在此方法之后的内存读取都要从memory中读取而不是cpu cache。
var isStop false;
var t new Thread(()
{var isSuccess false;while (!isStop){Thread.MemoryBarrier();isSuccess !isSuccess;}Trace.WriteLine(子线程执行成功);
});
t.Start();
Thread.Sleep(1000);
isStop true;
t.Join();
Trace.WriteLine(主线程执行结束);3VolatileRead解决共享变量Release模式下可以正常运行
var isStop 0;
var t new Thread(()
{var isSuccess false;while (isStop 0){Thread.VolatileRead(ref isStop);isSuccess !isSuccess;}Trace.WriteLine(子线程执行成功);
});
t.Start();
Thread.Sleep(1000);
isStop 1;
t.Join();
Trace.WriteLine(主线程执行结束);三、ThreadPool
.NET Framework2.0时代出现了一个线程池ThreadPool是一种池化思想如果需要使用线程就可以直接到线程池中去获取直接使用如果使用完毕在自动的回放到线程池去
1、ThreadPool好处
不需要程序员对线程的数量管控,提高性能防止滥用去掉了很多在Thread中没有必要的Api
2、线程池如何分配一个线程
QueueUserWorkItem方法将方法排入队列以便开启异步线程它有两个重载。
QueueUserWorkItem(WaitCallback callBack)WaitCallback是一个有一个object类型参数且无返回值的委托。QueueUserWorkItem(WaitCallback callBack, object state)WaitCallback是一个有一个object类型参数且无返回值的委托state即WaitCallback中需要的参数 不推荐这么使用存在拆箱装箱的转换问题影响性能。
//无参数
ThreadPool.QueueUserWorkItem(o this.DoSomething(张三));
//一个参数
ThreadPool.QueueUserWorkItem(o this.DoSomething(张三), 12345);3、线程等待
1定义一个监听ManualResetEvent 2通过ManualResetEvent.WaitOne等待 3等到ManualResetEvent.Set方法执行了主线程等待的这个WaitOne()就继续往后执行
ManualResetEvent resetEvent new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(o
{this.DoSomething(o.ToString());resetEvent.Set();
}, 张三);
resetEvent.WaitOne();4、线程池如何控制线程数量
如果通过SetMinThreads/SetMaxThreads来设置线程的数量不建议大家去这样控制线程数量这个数量访问是在当前进程中是全局的错误配置可能影响程序的正常运行
{//线程池中的工作线程数int workerThreads 4;//线程池中异步 I/O 线程的数目int completionPortThreads 4;//设置最小数量ThreadPool.SetMinThreads(workerThreads, completionPortThreads);
}
{int workerThreads 8;int completionPortThreads 8;//设置最大数量ThreadPool.SetMaxThreads(workerThreads, completionPortThreads);
}
{ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);Debug.WriteLine($当前进程最小的工作线程数量{workerThreads});Debug.WriteLine($当前进程最小的IO线程数量{completionPortThreads});
}
{ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);Debug.WriteLine($当前进程最大的工作线程数量{workerThreads});Debug.WriteLine($当前进程最大的IO线程数量{completionPortThreads});
}5、扩展一个定时器功能
1RegisterWaitForSingleObject类但是不常用.涉及到定时任务建议使用Quartz.Net
2System.threading命名空间下的Thread类通过查看源码构造函数中有四个参数
第一个是object参数的委托第二个是委托需要的值第三个是调用 callback 之前延迟的时间量以毫秒为单位第四个是 调用 callback 的时间间隔以毫秒为单位
//每隔3s开启一个线程执行业务逻辑
ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), new WaitOrTimerCallback((obj, b) this.DoSomething(张三)), hello world, 3000, false);
//效果类似于Timer定时器2秒后开启该线程然后每隔3s调用一次
System.Threading.Timer timer new System.Threading.Timer((n) this.DoSomething(李四), 1, 2000, 3000);四、Task
1、Task出现背景
在前面的章节介绍过Task出现之前微软的多线程处理方式有Thread→ThreadPool→委托的异步调用虽然也可以基本业务需要的多线程场景但它们在多个线程的等待处理方面、资源占用方面、线程延续和阻塞方面、线程的取消方面等都显得比较笨拙在面对复杂的业务场景下显得有点捉襟见肘了。正是在这种背景下Task应运而生。
Task是微软在.Net 4.0时代推出来的也是微软极力推荐的一种多线程的处理方式Task看起来像一个Thread实际上它是在ThreadPool的基础上进行的封装Task的控制和扩展性很强在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。
2、Task开启线程有哪些方式
1new Task().Start()
Task task new Task(()
{this.DoSomething(张三);
});
task.Start();
Taskint task2 new Taskint(()
{return DateTime.Now.Year;
});
task2.Start();
int result task2.Result;
Debug.WriteLine($result{result});2Task.Run()
Task.Run(()
{this.DoSomething(张三);
});
Taskint task2 Task.Runint(()
{return DateTime.Now.Year;
});
int result task2.Result;
Debug.WriteLine($result{result});3Task.Factory.StartNew()
TaskFactory factory Task.Factory;
factory.StartNew(()
{this.DoSomething(张三);
});
Taskint task2 factory.StartNewint(()
{return DateTime.Now.Year;
});
int result task2.Result;
Debug.WriteLine($result{result});4new Task().RunSynchronously()同步方式上面三种异步方式
Task task new Task(()
{this.DoSomething(张三);
});
task.RunSynchronously();
Taskint task2 new Taskint(()
{return DateTime.Now.Year;
});
task2.RunSynchronously();
int result task2.Result;
Debug.WriteLine($result{result});3、线程等待
1task.Wait()
等待task内部执行完毕才会往后直行卡主线程Task实例方法 task.Wait(1000);//等待1000毫秒后就往后直行不管有没有执行结束 task.Wait(TimeSpan.FromMilliseconds(1000));//等待1000毫秒后就往后直行不管有没有执行结束
Task task Task.Run(() {this.DoSomething(张三);});
task.Wait();
//task.Wait(TimeSpan.FromMilliseconds(1000));
//task.Wait(1000);2WaitAny
某一个任务执行结束后去触发一个动作卡主线程Task静态方法
数据有可能是来自于第三方接口缓存数据库查询的时候我们不确定开启几个线程同时查询只要一个返回了就返回界面
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询数据库); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询缓存); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询接口); }));
Task.WaitAny(taskList.ToArray());
Debug.WriteLine($查到数据返回界面);3WaitAll
所有任务执行完成后去触发一个动作卡主线程Task静态方法 数据是来自于第三方接口缓存数据库查询的时候开启几个线程同时查询等所有数据全部查询出来一起返回界面
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询数据库); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询缓存); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询接口); }));
Task.WaitAll(taskList.ToArray());
Debug.WriteLine($查到数据返回界面);4WhenAny
与下面ContinueWith配合执行,当传入的线程中任何一个线程执行完毕继续执行ContinueWith中的任务(属于开启新线程不卡主线程)Task静态方法
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询数据库); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询缓存); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询接口); }));
Task.WhenAny(taskList.ToArray()).ContinueWith((n) { Debug.WriteLine($查到数据返回界面); });5WhenAll
当其中所有线程执行完成后新开启了一个线程执行继续执行新业务所以执行过程中不卡主线程Task静态方法
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询数据库); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询缓存); }));
taskList.Add(factory.StartNew(() { Debug.WriteLine(查询接口); }));
Task.WhenAll(taskList.ToArray()).ContinueWith((n) { Debug.WriteLine($查到数据返回界面); });6ContinueWhenAny
某一个任务执行结束后去触发一个动作不卡主线程TaskFactory实例方法等价于WhenAnyContinueWith
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(obj Coding(张三, 数据库设计), 张三));
taskList.Add(factory.StartNew(obj Coding(李四, 接口对接), 李四));
taskList.Add(factory.StartNew(obj Coding(王五, Webapi), 王五));
taskList.Add(factory.StartNew(obj Coding(赵六, 前端页面), 赵六));
factory.ContinueWhenAny(taskList.ToArray(), ts
{Debug.WriteLine(${ts.AsyncState}同学开发完毕田七开始测试);
});7ContinueWhenAll
所有任务执行完成后去触发一个动作不卡主线程TaskFactory实例方法等价于WhenAllContinueWith
ListTask taskList new ListTask();
TaskFactory factory new TaskFactory();
taskList.Add(factory.StartNew(obj Coding(张三, 数据库设计), 张三));
taskList.Add(factory.StartNew(obj Coding(李四, 接口对接), 李四));
taskList.Add(factory.StartNew(obj Coding(王五, Webapi), 王五));
taskList.Add(factory.StartNew(obj Coding(赵六, 前端页面), 赵六));
factory.ContinueWhenAll(taskList.ToArray(), ts
{Debug.WriteLine($所有人开发完毕我们一起庆祝一下吃个饭);
});4、TaskCreationOptions枚举类详解
一个Task内部可以开启线程Task内部的线程可以理解为子线程Task为父线程创建Task实例的时候可以传入TaskCreationOptions枚举参数来影响线程的运行方式。
1None默认情况
父线程不会等待子线程执行结束才结束。
Task task new Task(()
{Debug.WriteLine($task--start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});Task task1 new Task(() {this.DoSomething(task1);});Task task2 new Task(() {this.DoSomething(task2);});task1.Start();task2.Start();Debug.WriteLine($task--end--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
});
task.Start();
task.Wait();2AttachedToParent
子线程附加到父线程父线程必须等待所有子线程执行结束才能结束相当于Task.WaitAll(task1, task2)。
Task task new Task(()
{Debug.WriteLine($task--start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});Task task1 new Task(() {this.DoSomething(task1);}, TaskCreationOptions.AttachedToParent);Task task2 new Task(() {this.DoSomething(task2);}, TaskCreationOptions.AttachedToParent);task1.Start();task2.Start();Debug.WriteLine($task--end--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
});
task.Start();
task.Wait();3DenyChildAttach
不允许子任务附加到父任务上反AttachedToParent和默认效果一样。
Task task new Task(()
{Debug.WriteLine($task--start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});Task task1 new Task(() {this.DoSomething(task1);}, TaskCreationOptions.AttachedToParent);Task task2 new Task(() {this.DoSomething(task2);}, TaskCreationOptions.AttachedToParent);task1.Start();task2.Start();Debug.WriteLine($task--end--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
}, TaskCreationOptions.DenyChildAttach);
task.Start();
task.Wait();4PreferFairness
相对来说比较公平执行的先申请的线程优先执行。
Task task1 new Task(()
{this.DoSomething(task1);
}, TaskCreationOptions.PreferFairness);
Task task2 new Task(()
{this.DoSomething(task2);
}, TaskCreationOptions.PreferFairness);
task1.Start();
task2.Start();5LongRunning
事先知道是长时间执行的线程就加这个参数线程调度会优化。
6RunContinuationsAsynchronously
强制以异步方式执行添加到当前任务的延续。
7HideScheduler
防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default当前计划程序。
5、TaskContinuationOptions枚举类详解
ContinueWith可以传入TaskContinuationOptions枚举类参数来影响线程的运行方式。
1None默认情况
任务顺序执行。
Task task1 new Task(()
{this.DoSomething(task1);
});Task task2 task1.ContinueWith(t {this.DoSomething(task2);});Task task3task2.ContinueWith(t
{this.DoSomething(task3);
});task1.Start();2LazyCancellation
取消该线程该线程的前一个线程和后一个线程顺序执行。
CancellationTokenSource source new CancellationTokenSource();
source.Cancel();Task task1 new Task(()
{this.DoSomething(task1);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, source.Token,TaskContinuationOptions.LazyCancellation, TaskScheduler.Current);Task task3 task2.ContinueWith(t
{this.DoSomething(task3);
});task1.Start();3ExecuteSynchronously
前后任务由同一个线程执行。
Task task1 new Task(()
{this.DoSomething(task1);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
},TaskContinuationOptions.ExecuteSynchronously);task1.Start();4NotOnRanToCompletion
延续任务必须在前面task非完成状态才能执行。
Task task1 new Task(()
{this.DoSomething(task1);//异常了表示未执行完成task2能执行//不异常表示执行完成task2不能执行throw new Exception(手动制造异常表示不能执行完毕);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.NotOnRanToCompletion);task1.Start();5OnlyOnRanToCompletion
延续任务必须在前面task完成状态才能执行和NotOnRanToCompletion正好相反。
Task task1 new Task(()
{this.DoSomething(task1);//异常了表示未执行完成task2不能执行//不异常表示执行完成task2能执行throw new Exception(手动制造异常表示不能执行完毕);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.OnlyOnRanToCompletion);task1.Start();6NotOnFaulted
延续任务必须在前面task完成状态才能执行效果和OnlyOnRanToCompletion差不多。
Task task1 new Task(()
{this.DoSomething(task1);//throw new Exception(手动制造异常);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.NotOnFaulted);task1.Start();7OnlyOnFaulted
延续任务必须在前面task未完成状态才能执行效果和NotOnRanToCompletion差不多。
Task task1 new Task(()
{this.DoSomething(task1);//throw new Exception(手动制造异常);
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.OnlyOnFaulted);task1.Start();8OnlyOnCanceled
前面的任务未被取消才执行后面的任务。
CancellationTokenSource cts new CancellationTokenSource();
Task task1 new Task(()
{this.DoSomething(task1);cts.Cancel();
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.OnlyOnCanceled);task1.Start();9NotOnCanceled
前面的任务被取消才执行后面的任务。
CancellationTokenSource cts new CancellationTokenSource();
Task task1 new Task(()
{this.DoSomething(task1);cts.Cancel();
});Task task2 task1.ContinueWith(t
{this.DoSomething(task2);
}, TaskContinuationOptions.NotOnCanceled);task1.Start();10PreferFairness
System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务这意味着较早安排的任务将更可能较早运行而较晚安排运行的任务将更可能较晚运行。
11LongRunning
指定某个任务将是运行时间长、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler 提示过度订阅可能是合理的。
12AttachedToParent
指定将任务附加到任务层次结构中的某个父级。
13DenyChildAttach
如果尝试附有子任务到创建的任务指定 System.InvalidOperationException 将被引发。
14HideScheduler
防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default当前计划程序。
6、延迟执行
Task.Delay(),一般和ContinueWith配合使用执行的动作就是ContinueWith内部的委托委托的执行有可能是一个全新的线程也有可能是主线程。
//开启线程后线程等待3000毫秒后执行动作不卡主线程
Task.Delay(3000).ContinueWith(t
{this.DoSomething(张三);
});五、Task进阶
1、多线程捕获异常
1线程不等待捕捉不到异常
多线程中如果发生异常使用try-catch包裹捕捉不到异常异常还没发生主线程已经执行结束
//捕捉不到异常
try
{Task task Task.Run(() {int i 0;int j 10;int k j / i; //尝试除以0会异常});
}
catch (AggregateException aex)
{foreach (var exception in aex.InnerExceptions){Debug.WriteLine($线程不等待异常{exception.Message});}
}2线程不等待线程内部捕捉异常
多线程中如果要捕捉异常可以在线程内部try-catch可以捕捉到异常
//捕捉到异常
try
{Task task Task.Run(() {try{int i 0;int j 10;int k j / i; //尝试除以0会异常}catch (Exception ex){Debug.WriteLine($线程内异常{ex.Message});}});
}
catch (AggregateException aex)
{foreach (var exception in aex.InnerExceptions){Debug.WriteLine($线程不等待异常{exception.Message});}
}运行结果 线程内异常Attempted to divide by zero.3线程等待能够捕获异常 多线程中如果要捕捉异常需要设置主线程等待子线程执行结束可以捕捉到异常 多线程内部发生异常后抛出的异常类型是system.AggregateException //捕捉到异常
try
{
Task task Task.Run(()
{int i 0;int j 10;int k j / i; //尝试除以0会异常
});
//线程等待
task.Wait();
}
catch (AggregateException aex)
{
foreach (var exception in aex.InnerExceptions)
{Debug.WriteLine($线程等待异常{exception.Message});
}
}运行结果 线程等待异常Attempted to divide by zero.2、线程取消
线程取消是不能从外部取消的线程取消的实质还是通过变量去控制程序的运行和结束正常结束或者发生异常结束
1标准取消Cancel 实例化一个CancellationTokenSource 包含了一个IsCancellationRequested属性属性值默认为false 包含了一个Cancel方法Cancel方法如果被执行IsCancellationRequested属性值马上更新成true 线程内部判断IsCancellationRequested值结束线程 包含了一个Token属性可以Register注册一个委托创建Task的时候传入线程结束后调用
//初始化一个CancellationTokenSource实例
CancellationTokenSource source new CancellationTokenSource();//注册一个线程取消后执行的逻辑
source.Token.Register(()
{this.DoSomething(张三);
});Task.Run(()
{while (!source.IsCancellationRequested){this.DoSomething(李四, 500);}
}, source.Token);//Thread.Sleep阻塞主线程
Thread.Sleep(2000);
//Cancel方法更新IsCancellationRequested的值
source.Cancel();运行结果 DoSomething--Start--李四--04--19:04:32.592
DoSomething--End--李四--04--19:04:33.094
DoSomething--Start--李四--04--19:04:33.096
DoSomething--End--李四--04--19:04:33.597
DoSomething--Start--李四--04--19:04:33.598
DoSomething--End--李四--04--19:04:34.100
DoSomething--Start--李四--04--19:04:34.101
DoSomething--Start--张三--01--19:04:34.587
DoSomething--End--李四--04--19:04:34.604
DoSomething--End--张三--01--19:04:36.5882延迟取消CancelAfter
CancellationTokenSource source new CancellationTokenSource();
//注册一个线程取消后执行的逻辑
source.Token.Register(()
{ this.DoSomething(张三);
});Task.Run(()
{while (!source.IsCancellationRequested){this.DoSomething(李四, 500);}
}, source.Token);//2s后自动取消等待取消期间不阻塞主线程
source.CancelAfter(new TimeSpan(0, 0, 0, 2));运行结果 DoSomething--Start--李四--04--18:49:48.601
DoSomething--End--李四--04--18:49:49.102
DoSomething--Start--李四--04--18:49:49.104
DoSomething--End--李四--04--18:49:49.606
DoSomething--Start--李四--04--18:49:49.607
DoSomething--End--李四--04--18:49:50.109
DoSomething--Start--李四--04--18:49:50.110
DoSomething--Start--张三--05--18:49:50.599
DoSomething--End--李四--04--18:49:50.612
DoSomething--End--张三--05--18:49:52.6023CancellationTokenSource构造函数取消
//2s后自动取消
CancellationTokenSource source new CancellationTokenSource(2000);
//注册一个线程取消后执行的逻辑
source.Token.Register(()
{this.DoSomething(张三);
});Task.Run(()
{while (!source.IsCancellationRequested){this.DoSomething(李四, 500);}
}, source.Token);运行结果 DoSomething--Start--李四--04--19:17:11.726
DoSomething--End--李四--04--19:17:12.228
DoSomething--Start--李四--04--19:17:12.229
DoSomething--End--李四--04--19:17:12.731
DoSomething--Start--李四--04--19:17:12.733
DoSomething--End--李四--04--19:17:13.235
DoSomething--Start--李四--04--19:17:13.237
DoSomething--Start--张三--05--19:17:13.720
DoSomething--End--李四--04--19:17:13.741
DoSomething--End--张三--05--19:17:15.7224CreateLinkedTokenSource组合取消
利用CreateLinkedTokenSource构建CancellationTokenSource的组合体其中任何一个体取消则组合体就取消
CancellationTokenSource source1 new CancellationTokenSource();
CancellationTokenSource source2 new CancellationTokenSource();
CancellationTokenSource source3 new CancellationTokenSource();
var combineSource CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token,source3.Token);
source2.Cancel();
Debug.WriteLine($source1.IsCancellationRequested{source1.IsCancellationRequested});
Debug.WriteLine($source2.IsCancellationRequested{source2.IsCancellationRequested});
Debug.WriteLine($source3.IsCancellationRequested{source3.IsCancellationRequested});
Debug.WriteLine($combineSource.IsCancellationRequested{combineSource.IsCancellationRequested});运行结果 source1.IsCancellationRequestedFalse
source2.IsCancellationRequestedTrue
source3.IsCancellationRequestedFalse
combineSource.IsCancellationRequestedTrue5CancellationToken类监控取消
取消之后调用ThrowIfCancellationRequested就会抛异常不取消不会抛异常
CancellationTokenSource source1 new CancellationTokenSource();
CancellationTokenSource source2 new CancellationTokenSource();
CancellationTokenSource source3 new CancellationTokenSource(); //等价于上面那句话
var combineSource CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token,source3.Token);
source2.Cancel();
try
{combineSource.Token.ThrowIfCancellationRequested();
}
catch (Exception)
{Debug.WriteLine(报错了);
}
Debug.WriteLine($source1.IsCancellationRequested{source1.IsCancellationRequested});
Debug.WriteLine($source2.IsCancellationRequested{source2.IsCancellationRequested});
Debug.WriteLine($source3.IsCancellationRequested{source3.IsCancellationRequested});
Debug.WriteLine($combineSource.IsCancellationRequested{combineSource.IsCancellationRequested});运行结果 报错了
source1.IsCancellationRequestedFalse
source2.IsCancellationRequestedTrue
source3.IsCancellationRequestedFalse
combineSource.IsCancellationRequestedTrue3、线程返回值
1线程开启类的返回值
Taskstring task1 Task.Run(()
{this.DoSomething(张三);return ok;
});
//要读取返回值会阻塞主线程
Debug.WriteLine($我是主线程我要读取子线程task1的返回值为{task1.Result}--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});运行结果 DoSomething--Start--张三--06--19:45:49.765
DoSomething--End--张三--06--19:45:51.773
我是主线程我要读取子线程task1的返回值为ok--01--19:45:51.7742线程延续类的返回值
Taskint task1 Task.Run(()
{this.DoSomething(task1);return 2;
});var task2 task1.ContinueWith((t)
{this.DoSomething(task2);//这里的t代表 task1var num t.Result 2;return num.ToString();
});Debug.WriteLine($我是主线程我要读取子线程task1的返回值为{task1.Result}--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
Debug.WriteLine($我是主线程我要读取子线程task2的返回值为{task2.Result}--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});运行结果 DoSomething--Start--task1--05--19:52:11.346
DoSomething--End--task1--05--19:52:13.349
我是主线程我要读取子线程task1的返回值为2--01--19:52:13.357
DoSomething--Start--task2--04--19:52:13.359
DoSomething--End--task2--04--19:52:15.361
我是主线程我要读取子线程task2的返回值为4--01--19:52:15.3633线程条件延续类
Taskint task1 Task.Run(()
{this.DoSomething(task1);return 2;
});
Taskint task2 Task.Run(()
{this.DoSomething(task2);return 4;
});var task Task.WhenAny(new Taskint[2] { task1, task2 });//下面的值可能是1也可能是2
Debug.WriteLine($我是主线程我要读取子线程的返回值为{task.Result.Result}--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});运行结果 DoSomething--Start--task2--04--09:05:57.574
DoSomething--Start--task1--05--09:05:57.575
DoSomething--End--task1--05--09:05:59.583
DoSomething--End--task2--04--09:05:59.583
我是主线程我要读取子线程的返回值为2--01--09:05:59.5874、线程安全
线程安全一段业务逻辑单线程执行和多线程执行后的结果如果完全一致是线程安全的否则就是线程不安全的
1线程安全产生的原因
线程的开启需要时间线程开启不阻塞主线程的执行循环10000次的时间不足够开启10000个线程的时间就会出现数据不足10000的现象出现 单线程执行这段循环完毕以后intlist中有10000条数据 Listint intlist new Listint();
for (int i 0; i 10000; i)
{intlist.Add(i);
}
Debug.WriteLine($intlist中有{intlist.Count}条数据);多线程执行这段循环完毕以后intlist中有多少条数据第一次执行9937第二次执行9976 Listint intlist new Listint();
ListTask tasklist new ListTask();
for (int i 0; i 10000; i)
{Task.Run(() {intlist.Add(i);});
}
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine($intlist中有{intlist.Count}条数据);2加锁解决线程安全问题
锁的本质是独占引用加锁是反多线程的可以解决线程安全问题但是不推荐大家使用加锁会影响性能锁的标准写法 private readonly static object obj_Lock new object(); 锁对象不要去锁String锁This
Listint intlist new Listint();
ListTask tasklist new ListTask();
for (int i 0; i 10000; i)
{Task.Run(() {lock (obj_Lock){intlist.Add(i);}});
}
Task.WaitAll(tasklist.ToArray());
Debug.WriteLine($intlist中有{intlist.Count}条数据);3分块执行解决线程安全问题
把执行的任务切割然后分别开启一个线程执行每一个线程内部执行的动作是单线程线程安全等待所有线程执行结束以后再做一个统一汇总
Listint intlist new Listint();
Listint intlist2 new Listint();
Listint intlist3 new Listint();
int Num1 3000;
int Num2 6000;
int Num3 10000;
ListTask taskList new ListTask();
taskList.Add(Task.Run(()
{for (int i 0; i Num1; i){intlist.Add(i);}
}));
taskList.Add(Task.Run(()
{for (int i Num1; i Num2; i){intlist2.Add(i);}
}));taskList.Add(Task.Run(()
{for (int i Num2; i Num3; i){intlist3.Add(i);}
}));
Task.WaitAll(taskList.ToArray());
intlist.AddRange(intlist2);
intlist.AddRange(intlist3);
Debug.WriteLine($intlist中有{intlist.Count}条数据);4使用线程安全对象
BlockingCollectionint blockinglist new BlockingCollectionint();
ConcurrentBagint conocurrentbag new ConcurrentBagint();
ConcurrentDictionarystring, int concurrentDictionary new ConcurrentDictionarystring, int();
ConcurrentQueueint concurrentQueue new ConcurrentQueueint();
ConcurrentStackint concurrentStack new ConcurrentStackint();5、解决中间变量问题
1单线程执行
for (int i 0; i 5; i)
{Debug.WriteLine($ThreadID{Thread.CurrentThread.ManagedThreadId.ToString(00)}_i{i});
}2多线程执行问题
Task开启线程的时候延迟开启在循环的时候不会阻塞主线程循环很快线程执行业务逻辑的时候循环已经结束了i已经变成5了所以打出来的都是5
for (int i 0; i 5; i)
{Task.Run(() {Debug.WriteLine($ThreadID{Thread.CurrentThread.ManagedThreadId.ToString(00)}_i{i});});
}3多线程执行中间变量
可以另外定义个变量在每次循环的时候赋值循环多少次就会有多少个k每个线程使用的是每一次循环内部的k
for (int i 0; i 5; i)
{int k i;Task.Run(() {Debug.WriteLine($ThreadID{Thread.CurrentThread.ManagedThreadId.ToString(00)}_k{ k});});
}六、Parallel
1、Parallel特点
可以传入多个委托多个委托中的内容是会开启线程来执行执行的线程可能是新的线程也可能是主线程会阻塞主线程相当于是主线程等待子线程执行结束
Parallel.Invoke(() this.DoSomething(张三),() this.DoSomething(李四),() this.DoSomething(王五),() this.DoSomething(赵六));运行结果 DoSomething--Start--赵六--04--11:01:20.666
DoSomething--Start--王五--06--11:01:20.666
DoSomething--Start--张三--01--11:01:20.666
DoSomething--Start--李四--05--11:01:20.666
DoSomething--End--赵六--04--11:01:22.668
DoSomething--End--王五--06--11:01:22.696
DoSomething--End--张三--01--11:01:22.702
DoSomething--End--李四--05--11:01:22.703可以传入options.MaxDegreeOfParallelism来限制开启的线程数量可以做到不影响线程池的线程数量又能控制当前执行所用的线程数量
ParallelOptions options new ParallelOptions();
options.MaxDegreeOfParallelism 2;
Parallel.Invoke(options,() this.DoSomething(张三),() this.DoSomething(李四),() this.DoSomething(王五),() this.DoSomething(赵六));运行结果 DoSomething--Start--张三--01--11:03:20.700
DoSomething--Start--李四--09--11:03:20.702
DoSomething--End--张三--01--11:03:22.704
DoSomething--Start--王五--01--11:03:22.706
DoSomething--End--李四--09--11:03:22.710
DoSomething--Start--赵六--09--11:03:22.711
DoSomething--End--王五--01--11:03:24.707
DoSomething--End--赵六--09--11:03:24.714把Parallel包在一个Task里面实现不卡主线程
Task.Run(()
{Parallel.Invoke(() this.DoSomething(张三),() this.DoSomething(李四),() this.DoSomething(王五),() this.DoSomething(赵六));
});2、Parallel.For
实现循环开启线程执行动作可以获取索引可以控制开启的线程数量
ParallelOptions options new ParallelOptions();
options.MaxDegreeOfParallelism 3;
Parallel.For(0, 10, options, index
{Debug.WriteLine($index{ index} 线程ID: {Thread.CurrentThread.ManagedThreadId.ToString(00)});
});运行结果 index3 线程ID: 05
index0 线程ID: 01
index6 线程ID: 04
index1 线程ID: 01
index7 线程ID: 04
index8 线程ID: 04
index4 线程ID: 05
index5 线程ID: 05
index2 线程ID: 01
index9 线程ID: 043、Parallel.ForEach
实现循环遍历数组开启线程执行动作可以获取数组值可以控制开启的线程数量
Listint intlist new Listint() { 1, 2, 3, 5, 7, 11, 13, 17 };
ParallelOptions options new ParallelOptions();
options.MaxDegreeOfParallelism 3;
Parallel.ForEach(intlist, options, s
{Debug.WriteLine($index{ s} 线程ID: {Thread.CurrentThread.ManagedThreadId.ToString(00)});
});运行结果 index1 线程ID: 01
index7 线程ID: 08
index2 线程ID: 01
index11 线程ID: 08
index5 线程ID: 01
index13 线程ID: 08
index3 线程ID: 09
index17 线程ID: 08七、await/async
1、async/await是什么
1C#5 (.NET4.5) 引入的语法糖 2C#7.1Main入口也可以 3C#8.0可以使用异步流awaitforeach可以释放对象await using
2、await/async用法
1无返回值有Async无Await
async是用来修饰方法如果单独出现方法会警告不会报错和普通的多线程方法没有什么区别不存在线程等待的问题
/// summary
/// 无返回值有Async无Await
/// /summary
private async void NoReturnAsyncNoAwait()
{Console.WriteLine($NoReturnAsyncNoAwait Start ThreadId{Thread.CurrentThread.ManagedThreadId}--{DateTime.Now.ToString(HH:mm:ss.fff)});Thread.Sleep(3000);Console.WriteLine($NoReturnAsyncNoAwait End ThreadId{Thread.CurrentThread.ManagedThreadId}--{DateTime.Now.ToString(HH:mm:ss.fff)});
}2无返回值有Async有Await
await在方法体内部只能放在async修饰的方法内必须放在task前面主线程到await这里就返回了执行主线程任务task中的任务执行完毕以后继续执行await后面的后续内容有可能是子线程也有可能是其他线程甚至有可能是主线程来执行类似ContinueWith回调await后面的后续内容
/// summary
/// 无返回值有Async有Await
/// /summary
private async void NoReturnAsyncAwait()
{Debug.WriteLine($NoReturnAsyncAwait--Start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});Task task Task.Run(() {Thread.Sleep(3000);Debug.WriteLine($Task.Run--NoReturnAsyncAwait--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});});//主线程到await这里就返回了执行主线程任务//task中的任务执行完毕以后继续执行await后面的后续内容有可能是子线程也有可能是其他线程甚至有可能是主线程来执行await task;Debug.WriteLine($NoReturnAsyncAwait--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});//类似ContinueWith回调await后面的后续内容//task.ContinueWith(t //{// Debug.WriteLine($NoReturnAsyncAwait--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});//});
}运行结果 btnAwaitAsync_Click--Start--01--13:40:10.935
NoReturnAsyncAwait--Start--01--13:40:10.954
btnAwaitAsync_Click--End--01--13:40:10.987
Task.Run--NoReturnAsyncAwait--05--13:40:13.992
NoReturnAsyncAwait--End--01--13:40:14.0343返回Task有Async有Await
async Task async void,Task和Task能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行 方法 /// summary
/// 无返回值返回Task有Async有Await
/// /summary
/// returns/returns
private async Task NoReturnTaskAsyncAwait()
{Debug.WriteLine($NoReturnTaskAsyncAwait--Start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});Task task Task.Run(() {Thread.Sleep(3000);Debug.WriteLine($Task.Run--NoReturnTaskAsyncAwait--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});});await task;Debug.WriteLine($NoReturnTaskAsyncAwait--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
}方法调用 Task task NoReturnTaskAsyncAwait();
Task.WhenAny(task).ContinueWith((a) {this.DoSomething(张三);
});运行结果 btnAwaitAsync_Click--Start--01--14:05:33.611
NoReturnTaskAsyncAwait--Start--01--14:05:33.624
btnAwaitAsync_Click--End--01--14:05:33.674
Task.Run--NoReturnTaskAsyncAwait--04--14:05:36.686
NoReturnTaskAsyncAwait--End--01--14:05:36.702
DoSomething--Start--张三--06--14:05:36.718
DoSomething--End--张三--06--14:05:38.7334返回Task int有Async有Await
要使用返回值就一定要等子线程计算完毕 方法 /// summary
/// 返回Taskint有Async有Await
/// /summary
/// returns/returns
private Taskint ReturnTaskIntAsyncAwait()
{Debug.WriteLine($ReturnTaskIntAsyncAwait--Start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});TaskFactory taskFactory new TaskFactory();Taskint iResult Task.Run(() {Thread.Sleep(3000);Debug.WriteLine($Task.Run--ReturnTaskIntAsyncAwait--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});return 123;});Debug.WriteLine($ReturnTaskIntAsyncAwait--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});return iResult;
}方法调用 Taskint result ReturnTaskIntAsyncAwait();
Debug.WriteLine($ReturnTaskIntAsyncAwait--Result--{result.Result}--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});运行结果 btnAwaitAsync_Click--Start--01--14:09:03.839
ReturnTaskIntAsyncAwait--Start--1--14:09:03.851
ReturnTaskIntAsyncAwait--End--1--14:09:03.874
Task.Run--ReturnTaskIntAsyncAwait--5--14:09:06.903
ReturnTaskIntAsyncAwait--Result--123--01--14:09:06.911
btnAwaitAsync_Click--End--01--14:09:06.9175返回Task实现多个任务顺序执行不阻塞
/// summary
/// 返回Task实现多个任务顺序执行不阻塞
/// /summary
/// returnsasync 就只返回long/returns
private async Task ReturnTaskAsyncAwaits()
{Debug.WriteLine($ReturnTaskAsyncAwaits--Start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});await Task.Run(() {this.DoSomething(task1);});await Task.Run(() {this.DoSomething(task2);}); await Task.Run(() {this.DoSomething(task3);});Debug.WriteLine($ReturnTaskAsyncAwaits--Start--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
}运行结果 btnAwaitAsync_Click--Start--01--14:38:07.608
ReturnTaskAsyncAwaits--Start--01--14:38:07.627
DoSomething--Start--task1--04--14:38:07.703
btnAwaitAsync_Click--End--01--14:38:07.709
DoSomething--End--task1--04--14:38:09.719
DoSomething--Start--task2--04--14:38:09.753
DoSomething--End--task2--04--14:38:11.773
DoSomething--Start--task3--05--14:38:11.781
DoSomething--End--task3--05--14:38:13.799
ReturnTaskAsyncAwaits--Start--01--14:38:13.8066在Winform中存在特殊处理
更改控件的值必须是(UI线程)主线程去执行这跟Winform设计有关系在Winform中await后面的内容都会让主线程来执行 计算方法 private async Tasklong CalculationAsync(long total)
{var task await Task.Run(() {Debug.WriteLine($This is CalculationAsync Start,ThreadId{Thread.CurrentThread.ManagedThreadId});long lResult 0;for (int i 0; i total; i){lResult i;}Debug.WriteLine($This is CalculationAsync End,ThreadId{Thread.CurrentThread.ManagedThreadId});return lResult;});return task; //这句话必须由主线程来执行线程在同一时刻只能做一件事儿
}方法调用 /// summary
/// 在Winform中存在特殊处理
/// /summary
private async Task TextAsyncResultChange()
{Debug.WriteLine($TextAsyncResultChange--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});long lResult await this.CalculationAsync(1_000_000);//更改控件的值必须是(UI线程)主线程去执行这跟Winform设计有关系在Winform中await后面的内容都会让主线程来执行this.textAsyncResult.Text lResult.ToString();Debug.WriteLine($TextAsyncResultChange--End--{Thread.CurrentThread.ManagedThreadId.ToString(00)}--{DateTime.Now.ToString(HH:mm:ss.fff)});
}运行结果 btnAwaitAsync_Click--Start--01--15:31:16.624
TextAsyncResultChange--End--01--15:31:16.636
This is CalculationAsync Start,ThreadId6
btnAwaitAsync_Click--End--01--15:31:16.735
This is CalculationAsync End,ThreadId6
TextAsyncResultChange--End--01--15:31:16.7873、async/await好处
1既要有顺序又要不阻塞降低了编程难度 2以同步编程的方式来写异步
4、async/await原理
如果给方法加上Async在底层会生成一个状态机一个对象在不同的状态可以执行的不同的行为 1实例化状态机 2把状态机实例交给一个build去执行 3整理线程的上下文 4stateMachine.MoveNext(); 5MoveNext如何执行先获取一个状态继续往后执行 6如果有异常抛出异常把状态重置为-2 7如果没有异常把状态重置重置为-2 8SetResult();把结果包裹成一个Task
5、async/await优势场景
计算机的计算任务可以分成两类计算密集型任务和IO密集型任务,async/await和Task相比降低了线程使用数量性能相当不能提高计算速度优势就是在同等硬件基础上系统的吞吐率更高对计算密集型任务没有优势IO密集型计算有优势常见的IO密集型任务有
1Web请求Api请求
2文件读写
3数据库请求
4跟第三方交互的非托管资源
八、多线程双色球项目
需求双色球投注号码由6个红色球号码和1个蓝色球号码组成红色球号码从01–33中选择不重复蓝色球号码从01–16中选择 界面 代码实现 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace MyThread
{public partial class FrmSSQ : Form{/// summary /// 多线程双色球项目/// 需求双色球投注号码由6个红色球号码和1个蓝色球号码组成红色球号码从01--33中选择不重复蓝色球号码从01--16中选择/// /summarypublic FrmSSQ(){InitializeComponent();}#region Data /// summary/// 红球集合 其实可以写入配置文件/// /summaryprivate string[] RedNums {01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33};/// summary/// 蓝球集合 球号码可以放在配置文件/// /summaryprivate string[] BlueNums {01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16};private bool IsGoOn true;private ListTask taskList new ListTask();private static object object_Lock new object();#endregion/// summary/// 点击开始/// /summary/// param namesender/param/// param namee/paramprivate void btnStart_Click(object sender, EventArgs e){#region 初始化动作this.btnStart.Text 运行ing;this.lblBlue.Text 00;this.lblRed1.Text 00;this.lblRed2.Text 00;this.lblRed3.Text 00;this.lblRed4.Text 00;this.lblRed5.Text 00;this.lblRed6.Text 00;#endregionthis.btnStart.Enabled false;this.btnStop.Enabled true;taskList.Clear();Thread.Sleep(1000);//1.读取界面上有多少个球号码//2.循环球号码个数每循环一次开启一个线程foreach (var control in this.gboSSQ.Controls){if (control is Label){Label label (Label)control; //只对lable处理if (label.Name.Contains(Blue)) //蓝色球{taskList.Add( Task.Run(() //开启一个线程{//目标需要让这个球不断的跳动变化//1.获取号码值---号码的区间 建议写在配置文件就应该在BlueNums数组中找数据//2.赋值//3.循环 while (IsGoOn){//数组找数据通过索引先确定索引找到1-15的随机值作为索引值然后去数组中取出数据//new Random().Next(0, 16);int index new RandomHelper().GetRandomNumberDelay(0, 16);string blueNum this.BlueNums[index];//this.lblBlue.Text blueNum;//不允许---需要让主线程来帮助完成这件事儿this.Invoke(new Action(() //子线程委托出去让主线程帮助完成一件事儿{lblBlue.Text blueNum;}));}}));}else //红色球{taskList.Add( Task.Run(() { while (IsGoOn){ int index new RandomHelper().GetRandomNumberDelay(0, 33);string redNum this.RedNums[index]; lock (object_Lock){var currentNumberlist GetCurrentNumberList();if (!currentNumberlist.Contains(redNum)){this.Invoke(new Action(() {label.Text redNum;}));} } //问题号码重复如何解决//1.赋值的时候判断是否有重复//2.在赋值的时候进行判断如果界面上没有重复数据就赋值否则就重新生成index重新获取值//3.锁}})); } }}}/// summary/// 获取界面上所有红色球的球号码/// /summary/// returns/returnspublic Liststring GetCurrentNumberList(){Liststring numberlist new Liststring();foreach (var control in this.gboSSQ.Controls){if (control is Label){Label label (Label)control; //只对lable处理if (label.Name.Contains(Red)){numberlist.Add(label.Text);} }}//写代码测试if (numberlist.Count(ss00)0 numberlist.Distinct().Count()6){Console.WriteLine(********************有重复************************);foreach (var num in numberlist){Console.WriteLine(num);}}return numberlist;}/// summary/// 点击结束 /// /summary/// param namesender/param/// param namee/paramprivate void btnStop_Click(object sender, EventArgs e){Task.Run(() {this.IsGoOn false;//还要等待所有线程都执行结束才能输出结果 Task.WaitAll(taskList.ToArray()); //死锁是主线程和子线程相互等待引起的 this.Invoke(new Action(() {this.btnStart.Text Start;this.btnStart.Enabled true;this.btnStop.Enabled false;this.IsGoOn true;}));ShowResult();}); }/// summary/// 弹框提示数据/// /summaryprivate void ShowResult(){MessageBox.Show(string.Format(本期双色球结果为{0} {1} {2} {3} {4} {5} 蓝球{6}, this.lblRed1.Text, this.lblRed2.Text, this.lblRed3.Text, this.lblRed4.Text, this.lblRed5.Text, this.lblRed6.Text, this.lblBlue.Text));}}
}运行效果