异步多线程 笔记
/// 同步异步:
/// 同步方法:发起调用,完成后才继续下一行;非常符合开发思维,有序执行;
/// 诚心诚意的请人吃饭,邀请Nick,Nick要忙一会儿,等着Nick完成后,再一起去吃饭
/// 异步方法:发起调用,不等待完成,直接进入下一行,启动一个新线程来完成方法的计算
/// 客气一下的请人吃饭,邀请亡五,亡五要忙一会儿,你忙着我去吃饭了,你忙完自己去吃饭吧
1.使用 action 执行多线程 示例
//代码示例 Action<string> action = this.DoSomethingLong; action.Invoke("btnAsync_Click_1"); //同步执行 action.BeginInvoke("btnAsync_Click_1", null, null); //新起线程异步执行 //1 回调:将后续动作通过回调参数传递进去,子线程完成计算后,去调用这个回调委托 IAsyncResult asyncResult = null;//是对异步调用操作的描述 AsyncCallback callback = ar => { Console.WriteLine($"{object.ReferenceEquals(ar, asyncResult)}"); Console.WriteLine($"btnAsyncAdvanced_Click计算成功了。{ar.AsyncState}。{Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }; asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "花生"); //会传进去 花生 =ar.AsyncState //2 通过IsComplate等待,卡界面--主线程在等待,边等待边提示 int i = 0; while (!asyncResult.IsCompleted) //asyncResult.IsCompleted 是否已完成本次任务 { if (i < 9) { Console.WriteLine($"中华民族复兴完成{++i * 10}%...."); } else { Console.WriteLine($"中华民族复兴完成99.999999%...."); } Thread.Sleep(200); } Console.WriteLine("中华民族复兴已完成,沉睡的东方雄狮已觉醒!");
//3 WaitOne等待,即时等待 限时等待 asyncResult.AsyncWaitHandle.WaitOne();//直接等待任务完成 asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成 asyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms,超时就不等了
//4 EndInvoke 即时等待,而且可以获取委托的返回值 一个异步操作只能End一次 Func<int> func = () => { Thread.Sleep(2000); return DateTime.Now.Hour; }; int iResult = func.Invoke();//22 IAsyncResult asyncResult = func.BeginInvoke(ar => { //int iEndResultIn = func.EndInvoke(ar); }, null); int iEndResult = func.EndInvoke(asyncResult);//22
2.Thread 执行多线程 示例
//启动线程 ParameterizedThreadStart method=o=>this.DoSomethingLong("btnThread_Click"); Thread thread = new Thread(method); thread.Start("123");//开启线程,执行委托的内容 ThreadStart method = () => { Thread.Sleep(5000); this.DoSomethingLong("btnThread_Click"); Thread.Sleep(5000); }; //线程操作 Thread thread = new Thread(method); thread.Start();//开启线程,执行委托的内容 thread.Suspend();//暂停 thread.Resume();//恢复 真的不该要的,暂停不一定马上暂停;让线程操作太复杂了 thread.Abort(); //线程销毁,线程是计算机资源,程序想停下线程,只能向操作系统通知(线程抛异常),会有延时/不一定能真的停下来 Thread.ResetAbort(); //线程恢复销毁
//1等待 while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(200);//当前线程休息200ms } //2Join等待 thread.Join();//运行这句代码的线程,等待thread的完成 thread.Join(1000);//最多等待1000ms //优先执行 thread.Priority = ThreadPriority.Highest;//最高优先级:优先执行,但不代表优先完成 甚至说极端情况下,还有意外发生,不能通过这个来控制线程的执行先后顺序 //前台线程 thread.IsBackground = false;//默认是false 前台线程,进程关闭,线程需要计算完后才退出,true 关闭进程,线程退出
基于thread封装一个回调,回调:启动子线程执行动作A--不阻塞--A执行完后子线程会执行动作B
//不带返回值的封装
private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback)
{ ThreadStart method = new ThreadStart(() => { threadStart.Invoke();
actionCallback.Invoke(); }); new Thread(method).Start(); }
ThreadWithCallBack(threadStart, actionCallBack);
//带返回值的封装 异步,非阻塞的 ,还能获取到最终计算结果 private Func<T> ThreadWithReturn<T>(Func<T> func) { T t = default(T); ThreadStart threadStart = new ThreadStart(() => { t = func.Invoke(); }); Thread thread = new Thread(threadStart); thread.Start(); return new Func<T>(() => { thread.Join(); //thread.ThreadState return t; }); } Func<int> funcThread = this.ThreadWithReturn(func);//非阻塞 Console.WriteLine("do something 1"); Console.WriteLine("do something 2"); Console.WriteLine("do something 3"); int iResult = funcThread.Invoke();//阻塞
3.ThreadPool 的应用
//启动 ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click1")); ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click2"), "昔梦"); //当前电脑最大workerThreads ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads); 当前电脑最小workerThreads ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin); //设置的线程池数量是进程全局的,委托异步调用--Task--Parrallel--async/await 全部都是线程池的线程,直接new Thread不受这个数量限制的(但是会占用线程池的线程数量) ThreadPool.SetMaxThreads(8, 8);//设置的最大值,必须大于CPU核数,否则设置无效 ThreadPool.SetMinThreads(2, 2); //等待 ManualResetEvent mre = new ManualResetEvent(false); //false---关闭---Set打开---true---WaitOne就能通过 //true---打开--ReSet关闭---false--WaitOne就只能等待 ThreadPool.QueueUserWorkItem(o => { this.DoSomethingLong("btnThreadPool_Click1"); mre.Set(); }); Console.WriteLine("Do Something else..."); Console.WriteLine("Do Something else..."); Console.WriteLine("Do Something else..."); mre.WaitOne(); Console.WriteLine("任务已经完成了。。。");
4.Task .NET3.0推出
Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续 Task.Delay(2000); //异步等待,一般结合后续其他方法使用 如: Task task = Task.Delay(2000) .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString ("00")}"); });//异步等待--等待2s后启动新任务 TaskFactory //开发可以多人合作---多线程--提升性能 TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => this.Coding("冰封的心", "Portal"))); taskList.Add(taskFactory.StartNew(() => this.Coding("随心随缘", " DBA "))); taskList.Add(taskFactory.StartNew(() => this.Coding("心如迷醉", "Client"))); taskList.Add(taskFactory.StartNew(() => this.Coding(" 千年虫", "BackService"))); taskList.Add(taskFactory.StartNew(() => this.Coding("简单生活", "Wechat"))); //谁第一个完成,获取一个红包奖励 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); //全部作业完成后,一起庆祝一下 taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")) //全部作业完成后,一起庆祝一下 taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")) //这里是为了保证 全部作业完成后,一起庆祝一下 ,这里将在前面5个任务执行完之后再执行。 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}"))); //ContinueWhenAny ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程 //阻塞当前线程,等着任意一个任务完成 Task.WaitAny(taskList.ToArray());//也可以限时等待 Console.WriteLine("有一个模块已完成"); //需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成 Task.WaitAll(taskList.ToArray()); Console.WriteLine("5个模块全部完成后"); //Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操作 //阻塞卡界面,是为了并发以及顺序控制 //网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll //列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了 扩展 ,假如说我想控制下Task的并发数量,该怎么做? 20个 List<Task> taskList = new List<Task>(); for (int i = 0; i < 10000; i++) { int k = i; if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20) { Task.WaitAny(taskList.ToArray()); taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); } taskList.Add(Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); })); }
5.Parallel
//Parallel并发执行多个Action 多线程的, //主线程会参与计算---阻塞界面 //等于TaskWaitAll+主线程计算 Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"), () => this.DoSomethingLong("btnParallel_Click_2"), () => this.DoSomethingLong("btnParallel_Click_3"), () => this.DoSomethingLong("btnParallel_Click_4"), () => this.DoSomethingLong("btnParallel_Click_5")); 或者 Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}")); 或者 Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}")); 使用控制并发线程数量,这里会阻塞主线程 ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); //有没有办法不阻塞? Task.Run(() => { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); });