Task 使用详细[基础操作,异步原则,异步函数,异步模式]

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 使用详细[基础操作,异步原则,异步函数,异步模式]

如果Task是长任务,可以添加TaskCreationOptions.LongRunning参数,使任务不运行在线程池上,有利于提升性能。

查看代码

Task.Factory.StartNew(() =>
{
    Console.WriteLine(
        $"TaskFactoryStartNew IsBackGround:{CurrentThread.IsBackground}, IsThreadPool:{CurrentThread.IsThreadPoolThread}");
}, TaskCreationOptions.LongRunning);

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

 1.2 Task 返回值/带参数

Task 有一个泛型子类Task<TResult>,允许返回一个值。

查看代码

Task<string> task =Task.Run(()=>SayHello("Jack"));

string SayHello(string name)
{
    return "Hello " + name;
}

Console.WriteLine(task.Result);

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

通过任务的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();
}

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

对于自治任务(没有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";
}

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

如果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";
}

Task 使用详细[基础操作,异步原则,异步函数,异步模式]

当任务出现错误时,必须处理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 使用详细[基础操作,异步原则,异步函数,异步模式]

这个方法类似Task.Delay()方法。

 

待续。。。

上一篇:防抖和节流方法和区别


下一篇:minio集群安装