一、Task的基本用法
1.Task的启动
Task代表着一个并发操作,启动一个基于线程Task的最简单方式是使用Task.Run(),调用时只需传入一个Action委托:
Task.Run(()=>Console.WriteLine("Hello"));
Task默认使用线程池中的线程,它们都是后台线程。
2.Wait方法
与线程的Join方法类型,调用Task的Wait方法可以阻塞当前方法,直到Task完成。
Task task = Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("task print hello");
});
Console.WriteLine(task.IsCompleted); //False
task.Wait();
Console.WriteLine(task.IsCompleted); //True
可以在Wait中指定一个超时时间和取消令牌来提前终止等待状态。
3.长任务
默认情况下,CLR会将任务运行在线程池线程上,这种线程适合执行短小的计算密集的任务。如果要执行长时间阻塞的操作,则可以按照以下方式避免使用线程池线程:
Task task = Task.Factory.StartNew(() =>Console.WriteLine("hello"),TaskCreationOptions.LongRunning);
在线程池上运行一个长时间执行的任务并不会造成问题,但如果要并行运行多个长时间任务(特别是会造成阻塞的任务),则会对性能造成影响。
这种情况下,相比于使用TaskCreationOptions.LongRunning,更好的方案是:
①如果运行的是I/O密集型任务,则使用TaskCompletionSource和异步函数通过回调函数而非使用线程实现并发性
②如果任务是计算密集型,则使用生产者/消费者队列可以控制这些任务造成的并发数量,避免出现线程和进程饥饿的问题。
4.Task的返回值
Task由一个泛型子类 Task<TResult>
,它运行任务返回一个值。如果在调用Task.Run时传入一个 Func<TResult>
委托(或者兼容的Lambda表达式)替代Action就可以获得一个Task<TResult>
对象:
Task<int> task = Task.Run(() =>
{
Console.WriteLine("into task");
return 3;
});
Console.WriteLine(task.Result); // 输出3
5.Task的取消
var cts = new CancellationTokenSource();
try
{
var task = Task.Delay(100000, cts.Token);
Thread.Sleep(2000);
cts.Cancel();
await task;
}
catch (TaskCanceledException e)
{
Console.WriteLine("Task canceled:" + e.Message);
}
finally { cts.Dispose(); }