因为多个线程之间的协调调度是一件非常麻烦而且容易出错(error-prone)的,.NET已经不建议开发人员直接使用Thread,而是使用.NET 4.0之后引入的Task。参考:托管线程处理的最佳做法
Task要完成的任务由Action和Func体现,对应着两个Task类:
要得到一个Task实例,必须指明相应的Action和Func。
最直观的方式是将Action或Func作为Task的构造函数参数传入:
Action getup = () => { Console.WriteLine("getUp()……"); };
Task t1 = new Task(getup);
或者:
Func<long> getup = () => { Console.WriteLine("getUp()……"); return DateTime.Now.Ticks; };
Task<int> t1 = new Task<int>(getup);
得到一个Task之后,还需要显式调用Start()才开始运行:
t1.Start();
演示:t1中的内容并没有输出到控制台,@想一想:为什么?
Console.ReadLine();
for (int i = 0; i < 10; i++) { Console.WriteLine($"第{i + 1}次:"); Console.WriteLine(); Task t1 = new Task(getup); t1.Start(); //new出来的task还需要显式调用Start()才开始运行 for (int j = 0; j < 5; j++) { Console.WriteLine($"after:t1.Start(): j={j}"); } Console.WriteLine(); }
演示运行结果:getup()和其后for循环代码不分先后的运行
@想一想@:如果Task需要的方法有参数,比如getup(name),怎么传递?
Action<int> getup = i => { Console.WriteLine($"getUp({i})……"); };
Task t1 = new Task(() => { getup(i); //闭包 });
注意:新开一个Task并不总是新开一个Thread!(参考:What is the difference between task and thread?)
如果Task需要一个线程,默认它会利用线程池来获取。
一个进程只有一个线程池,池中的线程都是:
current.IsBackground = true;
可以用IsThreadPoolThread检查:
Console.WriteLine(current.IsThreadPoolThread); //是否线程池线程
而且.NET中还有一个专门的TaskSchedular负责线程池中线程的调度。
大体上来说,它利用以下一些方式提供其运行效率:
绝大多数场景,我们使用.NET内置的调度就OK了。
获得已经开始运行的Task实例:
Task t1 = Task.Run(getup); //t1被创建的同时立即执行 Task t1 = Task.Factory.StartNew(getup); //适合于精确控制task实例的生成
推荐顺序:Task.Run() >Task.Factory.StartNew() >new Task()
还有其他的一些方法,比如同步运行(不异步执行了!)
t1.RunSynchronously();属性,比如状态、是否成功完成……
Console.WriteLine(t1.Status); Console.WriteLine(t1.IsCompletedSuccessfully);
Task有两个实例方法,会等着task执行完成之后再继续运行:
t1.Wait(); //当前(主)线程等着t1完成之后才继续运行
Console.WriteLine("t1.Result:" + t1.Result); //使用Result属性获得返回值
@想一想@:t1.Wait()和直接调用getup()有啥区别?
在Wait()或Result之前,还是存在异步执行的!
演示:getup()...和k={k}交替输出:
Func<long> getup = () => { for (int i = 0; i < 5; i++) { Console.WriteLine("getup()..."); } return DateTime.Now.Ticks; };
Task<long> t1 = Task.Run(getup); for (int k = 0; k < 5; k++) { Console.WriteLine($"k={k}"); } Console.WriteLine(t1.Result);
这就叫做阻塞(block)的等待:当前线程停下来,等这个task完成,其他啥事不做。
Task有一个Delay()方法,看上去就和Thread.Sleep()类似,
Task.Delay(1000); //Delay()获得的Task不能Start()
但是,运行它的话就会发现:不对劲,没反应(演示)
因为它的延迟是非阻塞的,即不会阻止当前线程的执行。
如果要实现和Thread.Sleep()类似的效果,就得这样做:
Task t1 = Task.Delay(2000); //非阻塞 //t1.Start(); 不能再Start() t1.Wait(); //阻塞 Console.WriteLine("………………");
.NET为我们提供了简洁优雅的异步方法,只需要两个关键字:
被async标记的方法被称为异步方法,
只有await没有async,报编译错误。
static async void Process() { await Task.Run(() => Console.WriteLine("async process...")); }
await,可以理解为:异步(async)等待,后接 awaitable 实例。
我们可以简单的把awaitable理解成Task。
异步方法一直同步运行,直到 await。
从 await 开始异步(分叉):
异步方法执行完毕,继续方法调用后内容。
static async void Process() { for (int i = 0; i < 5; i++) { Console.WriteLine($"before await {i} with thread ({Thread.CurrentThread.ManagedThreadId})"); } await Task.Run(() => //开始同Process()方法后的代码异步: { Thread.Sleep(1); for (int k = 0; k < 5; k++) { Console.WriteLine($"async processing {k} with thread ({Thread.CurrentThread.ManagedThreadId})..."); } }); //继续await后面的代码 for (int j = 0; j < 5; j++) { Console.WriteLine($"after await {j} with thread ({Thread.CurrentThread.ManagedThreadId})"); } }
Process(); for (int i = 0; i < 5; i++) { Thread.Sleep(1); Console.WriteLine($"after process() {i} with thread ({Thread.CurrentThread.ManagedThreadId})"); }
演示并体会:
异步方法中的 void 可以被直接替换成 Task(推荐),以便于该方法进一步的被 await 传递。
static async void OuterProcess() { await Process(); } static async Task Process()
void通常做为顶级(top-level)方法使用。
返回值被Task包裹,写成Task<T>,T指方法体内声明返回的类型
static async Task<long> Process() //但方法声明的返回是Task<long> { return DateTime.Now.Ticks; //return的是long
如果要获得当前时间的ticks,有两种办法:
Console.WriteLine(Process().Result);
long ticks = await Process();
@想一想@:这两种方式的区别?演示:略,以及:for循环在OuterProcess()方法中和方法调用后的区别:
static async void OuterProcess() { Console.WriteLine(await Process()); for (int i = 0; i < 5; i++) { Console.WriteLine($"after process() {i} with thread ({Thread.CurrentThread.ManagedThreadId})"); }
任务并行库(Task Parallel Library),在System.Threading 和System.Threading.Tasks名称空间下。
简化异步/并行开发,在底层实现:
以下都是基于Task的并行
for (int i = 0; i < 5; i++) { Console.WriteLine(); Parallel.Invoke( () => { Console.WriteLine(i + $":task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} begin in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} end in thread-{Thread.CurrentThread.ManagedThreadId}"); }, () => { Console.WriteLine(i + $":task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in begin in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in thread-{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"task-{Task.CurrentId} in end in thread-{Thread.CurrentThread.ManagedThreadId}"); } ); }
演示:运行结果。
观察其规律:两个Action异步/并发运行。
Parallel.For(0, 10, x => { Console.WriteLine(x); });
Parallel.ForEach(Enumerable.Range(1,10), x => Console.WriteLine(x));
可以使用Task的静态方法:
需要引入线程数组Task[]作为参数
Task[] tasks = { Task.Run(() => { Thread.Sleep(3); Console.WriteLine("1-洗脸"); }), Task.Run(() => { Thread.Sleep(2); Console.WriteLine("2-刷牙"); }), Task.Run(() => { Thread.Sleep(4); Console.WriteLine("3-吃早餐"); }), Task.Run(() => { Thread.Sleep(1); Console.WriteLine("4-背单词"); }) };
演示:
Task.WhenAll(tasks).Wait();
@想一想@:When方法有啥用呢?放在async方法中哟!
await Task.WhenAny(tasks); Console.WriteLine("after tasks……");
其他,见:J&C:多线程:current / 属性状态 / 异步和并发 / 异常捕获 / 线程安全 / join / 锁 / 池
多快好省!前端后端,线上线下,名师精讲
更多了解 加: