Task是FrameWork4.0开始引入的,FrameWork4.5又添加了一些功能,比如Task.Run(),async/await关键字等,
在.NET FrameWork4.5之后,基于任务的异步处理已经成为主流模式, (Task-based Asynchronous Pattern,TAP)基于任务的异步模式。
在使用异步函数之前,先看下Task的基本操作。
一. Task 基本操作
1.1 Task 启动方式
Task.Run(()=>Console.WriteLine("Hello Task"));
Task.Factory.StartNew(()=>Console.WriteLine("Hello Task"));
Task.Run是Task.Factory.StartNew的快捷方式。
启动的都是后台线程,并且默认都是线程池的线程
Task.Run(() => { Console.WriteLine( $"TaskRun IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); }); Task.Factory.StartNew(() => { Console.WriteLine( $"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}"); });
如果Task是长任务,可以添加TaskCreationOptions.LongRunning参数,使任务不运行在线程池上,有利于提升性能。
查看代码
Task.Factory.StartNew(() =>
{
Console.WriteLine(
$"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}");
}, TaskCreationOptions.LongRunning);
1.2 Task 返回值/带参数
Task 有一个泛型子类Task<TResult>,允许返回一个值。
查看代码
Task<string> task =Task.Run(()=>SayHello("Jack"));
string SayHello(string name)
{
return "Hello " + name;
}
Console.WriteLine(task.Result);
通过任务的Result属性获取返回值,这是会堵塞线程,尤其是在桌面客户端程序中,谨慎使用Task.Result,容易导致死锁!
同时带参数的方式也不是很合理,后面可以被async/await方式直接替代。
1.3 Task 异常/异常处理
当任务中的代码抛出一个未处理异常时,调用任务的Wait()或者Result属性时,异常会被重新抛出。
查看代码
var task = Task.Run(ThrowError);
try
{
task.Wait();
}
catch(AggregateException ex)
{
Console.WriteLine(ex.InnerException is NullReferenceException ? "Null Error!" : "Other Error");
}
void ThrowError()
{
throw new NullReferenceException();
}
对于自治任务(没有wait()和Result或者是延续的任务),使用静态事件TaskScheduler.UnobservedTaskException可以在全局范围订阅未观测的异常。
以便记录错误日志
1.4 Task 延续
延续通常由一个回调方法实现,该方法会在任务完成之后执行,延续方法有两种
(1)调用任务的GetAwaiter方法,将返回一个awaiter对象。这个对象的OnCompleted方法告知任务当执行完毕或者出错时调用一个委托。
查看代码
Task<string> learnTask = Task.Run(Learn);
var awaiter = learnTask.GetAwaiter();
awaiter.OnCompleted(() =>
{
var result = awaiter.GetResult();
Console.WriteLine(result);
});
string Learn()
{
Console.WriteLine("Learn Method Executing");
Thread.Sleep(1000);
return "Learn End";
}
如果learnTask任务出现错误,延续代码awaiter.GetResult()将重新抛出异常,其中GetResult可以直接得到原始的异常,如果用Result属性方法,只能解析AggergateException.
这种延续方法更适用于富客户端程序,延续可以提交到同步上下文,延续回到UI线程中。
当编写库文件,可以使用ConfigureAwait方法,延续代码一会运行在任务运行的线程上,从而避免不必要的切换开销。
查看代码
var awaiter =learnTask.ConfigureAwait(false).GetAwaiter();
(2)另一种方法使用ContiuneWith
查看代码
Task<string> learnTask = Task.Run(Learn);
learnTask.ContinueWith(antecedent =>
{
var result = learnTask.Result;
Console.WriteLine(result);
});
string Learn()
{
Console.WriteLine("Learn Method Executing");
Thread.Sleep(1000);
return "Learn End";
}
当任务出现错误时,必须处理AggregateException, ContiuneWith更适合并行编程场景。
1.5 TaskCompletionSource类使用
从如下源码中可以看出当实例化TaskCompletionSource时,构造函数会新建一个Task任务。
查看代码
public class TaskCompletionSource
{
private readonly Task _task;
/// <summary>Creates a <see cref="TaskCompletionSource"/>.</summary>
public TaskCompletionSource() => _task = new Task();
/// <summary>
/// Gets the <see cref="Tasks.Task"/> created
/// by this <see cref="TaskCompletionSource"/>.
/// </summary>
/// <remarks>
/// This property enables a consumer access to the <see cref="Task"/> that is controlled by this instance.
/// The <see cref="SetResult"/>, <see cref="SetException(Exception)"/>, <see cref="SetException(IEnumerable{Exception})"/>,
/// and <see cref="SetCanceled"/> methods (and their "Try" variants) on this instance all result in the relevant state
/// transitions on this underlying Task.
/// </remarks>
public Task Task => _task;
}
它的真正的作用是创建一个不绑定线程的任务。
eg: 可以使用Timer类,CLR在定时之后触发一个事件,而无需使用线程。
实现通用Delay方法:
查看代码
Delay(5000).GetAwaiter().OnCompleted(()=>{ Console.WriteLine("Delay End"); });
Task Delay(int millisecond)
{
var tcs = new TaskCompletionSource<object>();
var timer = new System.Timers.Timer(millisecond) { AutoReset = false };
timer.Elapsed += delegate
{
timer.Dispose();
tcs.SetResult(null);
};
timer.Start();
return tcs.Task;
}
这个方法类似Task.Delay()方法。
待续。。。