若是一个方法被调用,调用者须要等待该方法被执行完毕以后才能继续执行,则是同步。html
若是方法被调用后马上返回,即便该方法是一个耗时操做,也能马上返回到调用者,调用者不须要等待该方法,则称之为异步。编程
异步编程须要用到Task任务函数,不返回值的任务由 System.Threading.Tasks.Task 类表示。返回值的任务由 System.Threading.Tasks.Task<TResult> 类表示,该类从Task 继承。api
C#提供了基于任务的异步编程方法(TPL),更多资料在《.NET 中的并行编程》https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/多线程
先来个例子看下Task的基本用法。异步
static void Main(string[] args) { var task = new Task(() => { Console.WriteLine($"hello, task的线程ID为{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("task start...."); Thread.Sleep(2000); Console.WriteLine("task end...."); }); task.Start(); Console.WriteLine("main start...."); Thread.Sleep(1000); Console.WriteLine("main end...."); Console.ReadLine(); }
运行结果为:async
经过new方法建立,而后start启动。异步编程
经过Task.Factory.StartNew建立。函数
经过Task.Run建立。学习
Task task1 = new Task(() => Console.WriteLine($"hello, task 1的线程ID{Thread.CurrentThread.ManagedThreadId}");}); task1.Start(); Task task2 = Task.Run(() => {Console.WriteLine($"hello, task 2的线程ID{Thread.CurrentThread.ManagedThreadId}");}); Task task3 = Task.Factory.StartNew(() => {Console.WriteLine($"hello,task 3的线程ID为{Thread.CurrentThread.ManagedThreadId}");});
若是要等待任务完成可使用Task.Wait方法,使用WaitAll方法等待全部任务完成。若是等待多个任务可是只须要任何一个任务完成就能够执行下一步,则可使用WaitAny。spa
static void Main(string[] args) { var t1 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t1线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); var t2 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t2线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); // t1.Wait(); var t3 = Task.Run(() => { Console.WriteLine($"t3线程ID为{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("t3 start...."); Thread.Sleep(2000); Console.WriteLine("t3 end...."); });
Console.WriteLine("main start...."); Thread.Sleep(1000); Console.WriteLine("main end...."); Console.ReadLine(); }
运行效果:
若是打开t1.Wait(); 以下t1先执行。
若是t1.Wait()改成:Task.WaitAll(t1, t2);输出以下,先等待t1和t2执行完毕。
若是须要某个任务先执行完再运行主线程,则使用RunSynchronously就能够同步执行Task任务。下面的例子就是在当前的调度器上同步的执行任务t1。
var t1 = new Task(() =>
{ Thread.Sleep(100); Console.WriteLine($"t1线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); t1.RunSynchronously();
Console.WriteLine($"主线程ID为{ Thread.CurrentThread.ManagedThreadId}");
Console.ReadLine();
四、任务的状态和返回值
Task<Tresult>获取异步任务的返回值。
var t1 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t1线程ID为{ Thread.CurrentThread.ManagedThreadId}"); return "t1 return"; }); Console.WriteLine($"t1 return value {t1.Result}");
Task<Tresult>会阻塞任务直到任务返回,Task.Result给出任务的返回值给调用它的任务,下面例子显示了Result等待任务完成。
var cts = new CancellationTokenSource(); Task<int> t1 = new Task<int>(() => { try { //while (!cts.IsCancellationRequested) { //cts.Token.ThrowIfCancellationRequested(); Console.WriteLine($"t1 start 线程ID为{ Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"t1 end 线程ID为{ Thread.CurrentThread.ManagedThreadId}"); } return 1; } catch (Exception ex) { Console.WriteLine("---Exception---"); return 0; } }, cts.Token); t1.Start(); Console.WriteLine("---end---{0}", t1.Result);
输出结果以下,t.Result为1.
t.Result会致使阻塞直至等待的任务完成返回。若是使用返回值,那么异步方法中方法签名返回值为Task<T>,代码中的返回值也要为T。
static void Main(string[] args) { var cts = new CancellationTokenSource(); Task<int> t1 = new Task<int>(() => { try { //while (!cts.IsCancellationRequested) { //cts.Token.ThrowIfCancellationRequested(); Console.WriteLine($"t1 start 线程ID为{ Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); Console.WriteLine($"t1 end 线程ID为{ Thread.CurrentThread.ManagedThreadId}"); } return 1; } catch (Exception ex) { Console.WriteLine("---Exception---"); return 0; } }, cts.Token); t1.Start(); // Console.WriteLine("---end---{0}", t1.Result); var t2 = Task.Run(() => { Thread.Sleep(100); Console.WriteLine($"t2线程ID为{ Thread.CurrentThread.ManagedThreadId}"); }); Console.ReadLine(); } }
输出为:
若是打开注释Console.WriteLine("---end---{0}", t1.Result);则任务阻塞,等待t1执行完,拿到返回值t1.Result以后才继续执行,输出为:
任务出错或用户取消了操做,则须要用到任务的取消TaskCanceledException。
cts.Cancel()设置了cts.IsCancellationRequested为true,
而后cts.Token.ThrowIfCancellationRequested()这一句抛出了异常。
执行结果为:
若是主线程不延时或延时为10,则有可能在任务启动以前就中止了任务,就不会发生任务抛出异常了,因为系统调度问题,必定状况下输出与上面的同样,大部分状况输出为:
此外,也能够在task中查询标志。while (!cts.IsCancellationRequested),若是取消则不在执行。若是一次取消多个任务,可使用CancellationTokenSource.CreateLinkedTokenSource实现。
帮助文档:
从开始.NET Framework 4,.NET Framework 使用统一的模型来协做取消异步操做或长时间运行的同步操做涉及两个对象:
一个CancellationTokenSource对象,它提供取消标记经过其Token属性,并将取消消息经过调用其Cancel或CancelAfter方法。
一个CancellationToken对象,指示是否请求取消。
用于实现协做取消模型的常规模式是:
实例化 CancellationTokenSource 对象,此对象管理取消通知并将其发送给单个取消标记。
将 CancellationTokenSource.Token 属性返回的标记传递给每一个侦听取消的任务或线程。
调用CancellationToken.IsCancellationRequested从接收取消标记的操做的方法。 提供有关每一个任务或线程来响应取消请求的机制。 不管你选择取消操做和彻底操做方式,取决于你的应用程序逻辑。
调用 CancellationTokenSource.Cancel 方法以提供取消通知。 这将设置CancellationToken.IsCancellationRequested到取消标记的每一个副本上的属性true。
调用Dispose方法在您使用完CancellationTokenSource对象。
Task是基于Thread的,是比较高层级的封装,Task任务最终仍是须要Thread来执行。
Task比Thread的开销小,Task默认使用线程池。
Task默认使用后台线程来执行,Thread默认是前台线程。
Task使用参数和返回值都比Thread简单。
Task的多任务调度比Thread灵活,Thead的join须要挂起调用者去执行被调用线程而后返回到主线程。而Task提供了多种Wait函数,能够等待其余线程执行。
本文只是学习笔记,水平有限,抛砖迎玉。
一、基于任务的异步编程
二、Task 类
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.8
三、Difference Between Task and Thread
https://www.dotnetforall.com/difference-task-and-thread/
四、Task And Thread In C#
https://www.c-sharpcorner.com/article/task-and-thread-in-c-sharp/
五、C#多线程和异步(一)——基本概念和使用方法