异步多线程 2 Task
Task
task可以说是多线程的最佳实现:
1.task用的是线程池的线程,节省了资源,不用像Thread那样麻烦;
2.封装了丰富的方法,很方便。
创建task对象的方式有很多,这里介绍几个常用的:
用Start方法启动无参数无返回值的委托
直接new实例化Task对象并调用Start方法。
private static void AsyncMethod3() { Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); Action action = DoSomething; Task task = new Task(action); Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); task.Start(); } private static void DoSomething(string name) { Console.WriteLine("开始线程id:" + Thread.CurrentThread.ManagedThreadId.ToString() + " name:" + name); long result = 0; for (long i = 0; i < 1000000000; i++) { result += i; } Console.WriteLine("结束线程id:" + Thread.CurrentThread.ManagedThreadId.ToString() + " name:" + name); } private static void DoSomething() { DoSomething("我是默认姓名"); }
看,跟之前用委托的BeginInvoke效果一样。
用Run方法创建并启动,有参数无返回值的委托
这里用Run启动3个线程
private static void AsyncMethod3() { Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); Task.Run(() => DoSomething("张三")); Task.Run(() => DoSomething("李四")); Task.Run(() => DoSomething("王五")); Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); }
结果
等待方法WaitAll,全部等待
有时候多个业务可以多线程执行,但是需要等他们全部执行完成后再返回或通知,可以用WaitAll方法,阻塞当前线程,直到所有任务结束再执行下一行。
改一下上例的方法
List<Task> taskList = new List<Task>() { Task.Run(() => DoSomething("张三")), Task.Run(() => DoSomething("李四")), Task.Run(() => DoSomething("王五")) }; Task.WaitAll(taskList.ToArray());
结果
等待方法WaitAny,多个task完成任何一个就执行下面的代码
Task.WaitAny(taskList.ToArray());
结果
但是无论是WatiAll还是WatiAny,都有一个问题,那就是会阻塞主线程,所以这里再介绍一个新的开启线程的方法TaskFactory
TaskFactory
ContinueWhenAll:等待全部任务完成后,开启一个新task继续后续任务,不会阻塞主线程,其实就是集体回调的效果
TaskFactory taskFactory = new TaskFactory(); //等待全部任务完成后,启动一个新的task来完成后续任务 taskFactory.ContinueWhenAll(taskList.ToArray(), atArray => { Console.WriteLine("AsyncMethod3的TaskFactory.ContinueWhenAll线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); });
结果
ContinueWhenAny:任何一个线程任务完成后,开启一个新task来完成后续任务,不会阻塞主线程
taskFactory.ContinueWhenAny(taskList.ToArray(), atArray => { Console.WriteLine("AsyncMethod3的TaskFactory.ContinueWhenAny线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); });
WaitAll,WaitAny,ContinueWhenAll,ContinueWhenAny 熟练掌握着4种,基本也就能处理大部分的多线程业务了,注意的是后两种要配合TaskFactory。
回调,单个task的回调 ContinueWith
private static void AsyncMethod3() { Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); #region 回调 Task task = Task.Run(() => { DoSomething("张三"); }); task.ContinueWith(t => { Console.WriteLine("Task回调ContinueWith线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); }); #endregion Console.WriteLine("AsyncMethod3结束线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); }
回调的线程可能是已有线程3,也可能是新的线程4,这里是不确定的
返回值,其实用的是Func
private static void AsyncMethod3() { Console.WriteLine("AsyncMethod3开始线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); #region 返回值 var r = Task.Run<string>(() => GetApi("张三")); Console.WriteLine(r.Result); #endregion Console.WriteLine("AsyncMethod3结束线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); } private static string GetApi(string name) { Console.WriteLine("GetApi线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); long result = 0; for (long i = 0; i < 1000000000; i++) { result += i; } return "我是Api返回的值:" + name; }
可以看到,主线程里需要打印GetApi的返回值内容,所以主线程阻塞了,一直在等待,跟之前Func获取返回值是一样的情况,怎么解决,那就是放到回调函数里去。
var r = Task.Run<string>(() => GetApi("张三")); //Console.WriteLine(r.Result); //回调,将返回值放在回调里,不会阻塞主线程 Task task = Task.Run(() => { DoSomething("张三"); }); task.ContinueWith(t => { Console.WriteLine("Task回调ContinueWith线程:" + Thread.CurrentThread.ManagedThreadId.ToString()); Console.WriteLine(r.Result); });