c#_Task用法总结
Posted x1angzeeD.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c#_Task用法总结相关的知识,希望对你有一定的参考价值。
目录
4、Task的Wait、WaitAny、WaitAll方法介绍
6、Task的Wait、WaitAny、WaitAll方法介绍
Task与Thread
- Task是架构在Thread之上的,也就是说任务最终还是要抛给线程去执行。
- Task跟Thread不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:
◆ ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
◆ ThreadPool不支持线程执行的先后次序;
以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。在FCL4.0中,如果我们要编写多线程程序,Task显然已经优于传统的方式。
net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。
Task的用法
1、创建任务
无返回值的方式
方式1 : var t1 = new Task(() => TaskMethod("Task 1"));
t1.Start();
Task.WaitAll(t1);//等待所有任务结束
注 : 任务的状态:
Start之前为:Created
Start之后为:WaitingToRun
方式2 : Task.Run(() => TaskMethod("Task 2"));
方式3 : Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接异步的方法
或者 :
var t3=Task.Factory.StartNew(() => TaskMethod("Task 3"));
Task.WaitAll(t3);//等待所有任务结束
注 :
任务的状态:
Start之前为:Running
Start之后为:Running
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
class Program
static void Main(string[] args)
var t1 = new Task(() => TaskMethod("Task 1"));
var t2 = new Task(() => TaskMethod("Task 2"));
t2.Start();
t1.Start();
Task.WaitAll(t1, t2);
Task.Run(() => TaskMethod("Task 3"));
Task.Factory.StartNew(() => TaskMethod("Task 4"));
//标记为长时间运行任务,则任务不会使用线程池,而在单独的线程中运行。
Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);
#region 常规的使用方式
Console.WriteLine("主线程执行业务处理.");
//创建任务
Task task = new Task(() =>
Console.WriteLine("使用System.Threading.Tasks.Task执行异步操作.");
for (int i = 0; i < 10; i++)
Console.WriteLine(i);
);
//启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler)
task.Start();
Console.WriteLine("主线程执行其他处理");
task.Wait();
#endregion
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.ReadLine();
static void TaskMethod(string name)
Console.WriteLine("Task 0 is running on a thread id 1. Is thread pool thread: 2",
name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
带返回值的方式
方式4 :
Task<int> task = CreateTask("Task 1");
task.Start();
int result = task.Result;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
class Program
static Task<int> CreateTask(string name)
return new Task<int>(() => TaskMethod(name));
static void Main(string[] args)
TaskMethod("Main Thread Task");
Task<int> task = CreateTask("Task 1");
task.Start();
int result = task.Result;
Console.WriteLine("Task 1 Result is: 0", result);
task = CreateTask("Task 2");
//该任务会运行在主线程中
task.RunSynchronously();
result = task.Result;
Console.WriteLine("Task 2 Result is: 0", result);
task = CreateTask("Task 3");
Console.WriteLine(task.Status);
task.Start();
while (!task.IsCompleted)
Console.WriteLine(task.Status);
Thread.Sleep(TimeSpan.FromSeconds(0.5));
Console.WriteLine(task.Status);
result = task.Result;
Console.WriteLine("Task 3 Result is: 0", result);
#region 常规使用方式
//创建任务
Task<int> getsumtask = new Task<int>(() => Getsum());
//启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler)
getsumtask.Start();
Console.WriteLine("主线程执行其他处理");
//等待任务的完成执行过程。
getsumtask.Wait();
//获得任务的执行结果
Console.WriteLine("任务执行结果:0", getsumtask.Result.ToString());
#endregion
static int TaskMethod(string name)
Console.WriteLine("Task 0 is running on a thread id 1. Is thread pool thread: 2",
name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
Thread.Sleep(TimeSpan.FromSeconds(2));
return 42;
static int Getsum()
int sum = 0;
Console.WriteLine("使用Task执行异步操作.");
for (int i = 0; i < 100; i++)
sum += i;
return sum;
2、async/await的实现方式
(异步方法:在执行完成前立即返回调用方法,在调用方法继续执行的过程中完成任务。)
①从 Main 方法执行到 CountCharactersAsync(1, url1) 方法时,该方法会立即返回,然后才会调用它内部的方法开始下载内容。该方法返回的是一个 Task<int> 类型的占位符对象,表示计划进行的工作。这个占位符最终会返回 int 类型的值。
②这样就可以不必等 CountCharactersAsync(1, url1) 方法执行完成就可以继续进行下一步操作。到执行 CountCharactersAsync(2, url2) 方法时,跟 ① 一样返回 Task<int> 对象。
③然后,Main 方法继续执行三次 ExtraOperation 方法,同时两次 CountCharactersAsync 方法依然在持续工作 。
④t1.Result 和 t2.Result 是指从 CountCharactersAsync 方法调用的 Task<int> 对象取结果,如果还没有结果的话,将阻塞,直有结果返回为止。
async/await 结构可分成三部分:
- 调用方法:该方法调用异步方法,然后在异步方法执行其任务的时候继续执行;
- 异步方法:该方法异步执行工作,然后立刻返回到调用方法;
- await 表达式:用于异步方法内部,指出需要异步执行的任务。一个异步方法可以包含多个 await 表达式(不存在 await 表达式的话 IDE 会发出警告)。
- 注意await异步等待的地方,await后面的代码和前面的代码执行的线程可能不一样
- async关键字创建了一个状态机;await会解除当前线程的阻塞,完成其他任务
语法分析:
- 关键字:方法头使用 async 修饰。
- 要求:包含 N(N>0) 个 await 表达式(不存在 await 表达式的话 IDE 会发出警告),表示需要异步执行的任务。
- 返回类型:只能返回 3 种类型(void、Task 和 Task<T>)。Task 和 Task<T> 标识返回的对象会在将来完成工作,表示调用方法和异步方法可以继续执行。
- 参数:数量不限,但不能使用 out 和 ref 关键字。
- 命名约定:方法后缀名应以 Async 结尾。
- 其它:匿名方法和 Lambda 表达式也可以作为异步对象;async 是一个上下文关键字;关键字 async 必须在返回类型前。
3、task可以同步执行吗?
通过上面的实际代码测试,我们知道task都是异步执行,那么有人会问,task可以实现同步执行吗?不急,强大的微软也想到了这个问题,于是乎,提供了
1)task.RunSynchronously()
来同步执行,但是这种方式执行,只有通过new 实例化的task才有效,原因也很简单,其他两种方式创建task都已经自启动执行了,不可能在来一个同步启动执行吧,嘿嘿。下面我们用代码来演示:
/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
Console.WriteLine("主线程开始执行!");
Task<string> task = new Task<string>(() =>
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
);
/// task.Start();
/// task.Wait();
// 获取执行结果,会阻塞主流程
// string result = task.Result;
//同步执行,task会阻塞主线程
task.RunSynchronously();
Console.WriteLine("执行主线程结束!");
Console.ReadKey();
执行结果:很明显主线程阻塞等待task同步执行。
2)task同步执行,出了上面的实现方式,其实我们也可以通过task.wait()来变相的实现同步执行效果,当然也可以用task.Result来变现的实现,原理很简单,因为wait()和Result都是要阻塞主流程,直到task执行完毕,是不是有异曲同工之妙呢!以代码为例:
通过task.wait()实现,只需要对上面的代码做一个简单的调整,如下:其最终的效果一样:
/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
Console.WriteLine("主线程开始执行!");
Task<string> task = new Task<string>(() =>
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
);
task.Start();
task.Wait();
// 获取执行结果,会阻塞主流程
// string result = task.Result;
// 同步执行,task会阻塞主线程
// task.RunSynchronously();
Console.WriteLine("执行主线程结束!");
Console.ReadKey();
执行结果:
3)通过task.Result 实现,前提是task一定要有返回值,如下:其最终的效果一样:
/// <summary>
/// 通过RunSynchronously 实现task的同步执行
/// </summary>
private static void TaskRunSynchronously()
Console.WriteLine("主线程开始执行!");
Task<string> task = new Task<string>(() =>
Thread.Sleep(100);
Console.WriteLine("Task执行结束!");
return "";
);
task.Start();
/// task.Wait();
// 获取执行结果,会阻塞主流程
string result = task.Result;
// 同步执行,task会阻塞主线程
// task.RunSynchronously();
Console.WriteLine("执行主线程结束!");
Console.ReadKey();
执行效果也和上面的两种方式一样。
当然我还可以通过task.IsCompleted来变现实现,在此就不在细说,简单一个代码示意即可:while
(!task.IsCompleted)
当然我上面说的几种实现同步的方式,只是为了拓展一下思路,不一定都是最优方案。
4、Task的Wait、WaitAny、WaitAll方法介绍
task的基本创建和用法,上面都做了简单的介绍,但是在我们实际业务场景中,往往不是那么简单的单纯实现。比如:还是刚刚上面的那个酒店信息获取为例,现在新的需求是:3个渠道的接口实时数据,我们只需要获取到其中的一个就立即返回会用户,避免用户等待太久,那么这个时候task.WaitAny就派上用场了,WaitAny就是等待一组tsak集合中,只要有一个执行完毕就不在等待,与之对应的是WaitAll需要等待一组tsak集合中所有tsak都执行完毕,当然了Wait是针对一个task的,等待本身执行完成,上面的模拟同步执行已经说了,就不在啰嗦。
/// <summary>
/// 获取最新的客房信息(只需要获取到一个即可)
/// </summary>
/// <returns>客房信息集合</returns>
private static List<string> GetOneHotelRoomInfro()
// 模拟存储获取到的酒店客房数据集合
List<string> listHotelRoomInfro = new List<string>();
Console.WriteLine("下面通过3个task,并行的到不同接口方获取实时的客房信息
:");
Console.WriteLine("");
// 在此我也分别对3种不同渠道,采用3种不同的方式来实现
// 其一、通过传统的 new 方式来实例化一个task对象,获取 携程 的客房数据
Task newCtripTask = new Task(() =>
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 携程 的最新客房信息");
);
// 启动 tsak
newCtripTask.Start();
// 其二、通过工厂 factory 来生成一个task对象,并自启动:获取 艺龙
的客房数据
Task factoryElongTask = Task.Factory.StartNew(() =>
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 艺龙 的最新客房信息");
);
// 其三、通过 Task.Run(Action action) 来创建一个自启动task:
获取 去哪儿网 的客房数据
Task runQunarTask = Task.Run(() =>
// 具体获取业务逻辑处理...
Thread.Sleep(new Random().Next(100, 1000));
listHotelRoomInfro.Add("我是来自 去哪儿网 的最新客房信息");
);
// 只需要等待一个有返回即可
Task.WaitAny(new Task[] newCtripTask, factoryElongTask,
runQunarTask );
// 等待所有接口数据返回
// Task.WaitAll(new Task[] newCtripTask, factoryElongTask,
runQunarTask );
Console.WriteLine("已经有接口数据返回!");
foreach (var item in listHotelRoomInfro)
Console.WriteLine($"返回的接口数据为:item");
Console.WriteLine("主线程执行完毕!");
Console.ReadKey();
Console.WriteLine("");
return listHotelRoomInfro;
5、释放、取消Task
1)Task实现了IDispose接口,而且提供了Dispose方法。这意味着我应该Dispose所有的Task吗?
这是我简短的回答:不,不用非要Dispose你的Task。
这是我中等长度的答案:不用。不用费心去Dispose你的Task,除非性能或者弹性测试需要你去基于使用方式去Dispose Task来达到性能目标。当你需要去Dispose 那些Task,仅在情形简单的时候去做,即当你100%确认代码中Task已经成功完成(IsCompleted为true)而且没有其他人使用这些Task。
如果有喝咖啡并阅读的时间,可以看看下面的长答案:在高的设计层面,.Net Framework设计准备表明如果一个类型持有其他的IDispose资源,那么它应该实现IDispose接口。所以Task有Dispose方法。在内部,Task可以分配一个被用来等待Task成功完成的WaitHandle。WaitHandle实现了IDispose接口,因为它内部持有实现了IDispose的SafeWaitHandle。SafeWaitHandle包含一个本地Handle资源:如果SafeWaitHandle没有Dispose,最终它的终结器(finalizer)会清理所有的被包含的handle资源,但是与此同时它的资源不会被清理干净,这会给系统造成压力。通过Task实现IDispose接口,我们使得关心积极清理这些资源的开发者可以及时地清理这些资源。
带来的问题
如果每一个Task分配一个WaitHandle,出于性能考虑,积极地dispose这些task是个好主意。但事实并非如此。事实上很少的task真正地分配了WaitHandle。在.Net 4里面,WaitHandle在几种情况下被延迟加载:
-
如果Task.IAsyncResult.AsyncWaitHandle属性(显式实现了接口)被访问;
-
如果Task被Task.WaitAll或者Task.WaitAny调用,且Task尚未成功完成。
而且在.Net 4中,一旦Task被dispose了,它的大多数成员在被访问的时候会抛出ObjectDisposedExceptions异常。这使得缓存已经完成的task变得困难(可能因为性能原因缓存task),因为一旦一个消费者dispose了某个task,另一个消费者将不能访问这个task的重要成员,比如ContinueWith或者这个task的Result。
2)CancellationTokenSource用于取消基于Task建立的线程(单个线程、多个线程都可以)
如果 CacellationToken 在 Task 调度前取消, Task 会被取消,永远都不会执行。但是,如果Task 已调度,那么Task 为了允许它的操作在执行期间取消,Task 的代码就必须显示支持取消。也就是在一个已经开始执行的任务时,单单使用Cancel 函数是不起作用的,任务根本不会被取消。
CancellationTokenSource cts = new CancellationTokenSource ();
Task.Run(() =>…… , cts.Token);//将cts.Token传入任务中,在外部通过控制cts实现对任务的控制
cts.Cancel();//传达取消请求
bool taskState = cts.IsCancellationRequested;//判断任务是否取消,放while中用于跳出循环
CancellationTokenSource的功能不仅仅是取消任务执行,我们可以使用 source.CancelAfter(5000)
实现5秒后自动取消任务,也可以通过 source.Token.Register(Action action)
注册取消任务触发的回调函数,即任务被取消时注册的action会被执行。
static void Main(string[] args)
CancellationTokenSource source = new CancellationTokenSource();
//注册任务取消的事件
source.Token.Register(() =>
Console.WriteLine("任务被取消后执行xx操作!");
);
int index = 0;
//开启一个task执行任务
Task task1 = new Task(() =>
while (!source.IsCancellationRequested)
Thread.Sleep(1000);
Console.WriteLine($"第++index次执行,线程运行中...");
);
task1.Start();
//延时取消,效果等同于Thread.Sleep(5000);source.Cancel();
source.CancelAfter(5000);
Console.ReadKey();
执行结果如下,第5次执行在取消回调后打印,这是因为,执行取消的时候第5次任务已经通过了while()判断,任务已经执行中了:
6、Task的Wait、WaitAny、WaitAll方法介绍
Wait/WaitAny/WaitAll方法返回值为void,这些方法单纯的实现阻塞线程。我们现在想让所有task执行完毕(或者任一task执行完毕)后,开始执行后续操作,怎么实现呢?
这时就可以用到WhenAny/WhenAll方法了,这些方法执行完成返回一个task实例。 task.WhenAll(Task[] tasks) 表示所有的task都执行完毕后再去执行后续的操作, task.WhenAny(Task[] tasks) 表示任一task执行完毕后就开始执行后续操作。
static void Main(string[] args)
Task task1 = new Task(() =>
Thread.Sleep(500);
Console.WriteLine("线程1执行完毕!");
);
task1.Start();
Task task2 = new Task(() =>
Thread.Sleep(1000);
Console.WriteLine("线程2执行完毕!");
);
task2.Start();
//task1,task2执行完了后执行后续操作
Task.WhenAll(task1, task2).ContinueWith((t) =>
Thread.Sleep(100);
Console.WriteLine("执行后续操作完毕!");
);
Console.WriteLine("主线程执行完毕!");
Console.ReadKey();
执行结果如下,我们看到WhenAll/WhenAny方法不会阻塞主线程,当使用WhenAll方法时所有的task都执行完毕才会执行后续操作;如果把栗子中的WhenAll替换成WhenAny,则只要有一个线程执行完毕就会开始执行后续操作,这里不再演示。
以上是关于c#_Task用法总结的主要内容,如果未能解决你的问题,请参考以下文章