C# 之 多线程 -- 任务概念以及使用示例 ( Task | TaskCompletionSource | Async | Await )

Posted 陈言必行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 之 多线程 -- 任务概念以及使用示例 ( Task | TaskCompletionSource | Async | Await )相关的知识,希望对你有一定的参考价值。

一,相关关键字和运算符

1.1 Async/Await 介绍和使用示例

  • 关键字 Async
    使用 ‘async’ 修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法 。

    ‘async’ 关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。

    如果 ‘async’ 关键字修改的方法不包含 ‘await’ 表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含 ‘await’ 语句的任何异步方法,因为该情况可能表示存在错误。

定义异步方法:

public async Task<string> AsyncTest()
{
 
}

  • 运算符 Await

    ‘await’ 运算符暂停对其所属的 ‘async’ 方法的求值,直到其操作数表示的异步操作完成。 异步操作完成后,如果有返回值 ‘await’ 运算符将返回操。 当 ‘await’ 运算符应用到表示已完成操作的操作数时,它将立即返回操作的结果,而不会暂停其所属的方法。 ‘await’ 运算符不会阻止计算异步方法的线程。 当 ‘await’ 运算符暂停其所属的异步方法时,控件将返回到方法的调用方法。

使用Await运算符:

public async Task<string> AsyncTest()
{
    // 等AsyncTest_1 执行完成
    await AsyncTest_1();
}

public async Task<string> AsyncTest_1()
{
    return "任务AsyncTest_1 执行完成";
}

1.2 Async/Await 异步编程中的最佳做法

此部分(1.3)取自 --> MSDN


二, Task 类

2.1 Task定义

Task类表示不返回值并且通常以异步方式执行的单个操作。 Task 对象是在 .NET Framework 4 中首次引入的 基于任务的异步模式 的中心组件之一。 由于对象执行的工作 Task 通常在线程池线程上异步执行,而不是在主应用程序线程上同步执行,因此可以使用 Status 属性以及 IsCanceled 、 IsCompleted 和 IsFaulted 属性来确定任务的状态。 通常,lambda 表达式用于指定任务要执行的工作。

2.2 属性方法

属性列表

属性名说明
AsyncState获取在创建 Task 时提供的状态对象,如果未提供,则为 null。(只读)
CompletedTask此属性将返回其 Status 属性设置为的任务 RanToCompletion 。 若要创建一个返回值并运行到完成的任务,请调用 FromResult 方法。(只读)
CreationOptions获取用于创建此任务的 TaskCreationOptions。(只读)
CurrentId返回当前正在执行 Task 的 ID。(只读)
Exception获取导致 AggregateException 提前结束的 Task。 如果 Task 成功完成或尚未引发任何异常,这将返回 null。(只读)
Factory一个工厂对象,可创建多种 TaskTask<TResult> 对象。提供对用于创建和配置 Task 和 Task 实例的工厂方法的访问。
Id任务 Id 按需分配,不一定表示任务实例的创建顺序,有可能存在冲突。若要从任务正在执行的代码内获取当前正在执行的任务的任务 ID,请使用 CurrentId 属性。
IsCanceled如果任务由于被取消而完成,则为 true;否则为 false。
IsCompletedtrue 如果任务已完成 (即,任务处于以下三个最终状态之一: RanToCompletionFaultedCanceled) ,则为; 否则为 false 。
IsCompletedSuccessfullytrue 如果任务运行到完成,则为;否则为 false 。
IsFaulted如果任务引发了未经处理的异常,则为 true;否则为 false。
Status此任务实例的当前 TaskStatus

方法列表

方法名方法说明
ConfigureAwait(bool)参数:尝试将延续任务封送回上下文,则为 true;否则为 false。 返回值:尝试将延续任务封送回原始上下文,则为 true;否则为 false。
ContinueWith()创建一个在目标 Task 完成时异步执行的延续任务。即完成一个任务开启下一个任务。
Delay()创建将在时间延迟后完成的任务。在完成返回的任务前要等待的参数毫秒数;如果无限期等待,则为 -1。
Dispose()释放 Task 类的当前实例所使用的所有资源。
FromCanceled()创建 Task 或者 Task<TResult>,它因指定的取消标记进行的取消操作而完成。
FromException()创建 Task 或者 Task<TResult>,它在完成后出现指定的异常。
FromResult(TResult)类型参数TResult 任务返回的结果的类型。参数 TResult 存储入已完成任务的结果。返回值: Task<TResult>已成功完成的任务。
GetAwaiter()获取用于等待此 Taskawaiter。返回:一个 awaiter 实例。
Run()将在线程池上运行的指定工作排队,并返回该工作的任务或 Task<TResult> 句柄。
RunSynchronously()对当前的 Task 同步运行 TaskScheduler
Start()启动 Task,并将它安排到当前的 TaskScheduler 中执行。
Wait()等待当前 ‘Task’ 完成执行过程。
WaitAll()等待所有提供的 Task 对象完成执行过程。
WaitAny()等待提供的任一 Task 对象完成执行过程。
WhenAll()所有提供的任务已完成时,创建将完成的任务。
WhenAny()任何提供的任务已完成时,创建将完成的任务。
Yield()创建异步产生当前上下文的等待任务。可以 await Task.Yield(); 在异步方法中使用来强制异步完成方法。

1.3 Task使用

Task最厉害的地方就是他的任务控制了,你可以很好的控制task的执行顺序,让多个task有序的工作。

任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。

一个最简单的示例: 开启任务1 --> 调用并等待任务2完成 --> 继续执行任务1

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskTest
{
    class Program
    {
        static void Main(string[] args)
        {

            Task<string> result = task_1();

            Console.WriteLine(result.Result);

            Console.ReadKey();
        }

        static async Task<string> task_1()
        {
            Console.WriteLine("任务1 开始执行");

            Console.WriteLine("等待任务2 执行完成... ");

            string res = await task_2();

            Console.WriteLine("任务2 执行完成... 返回值: " + res);

            return "任务1执行完成返回值";
        }

        static async Task<string> task_2()
        {
            Thread.Sleep(1000);
            return "任务2执行完成";
        }

    }
}

执行结果:


三,TaskCompletionSource 类

3.1 概念定义

TaskCompletionSource :表示未绑定到委托的 Task 的制造者方,并通过 Task 属性提供对使用者方的访问。

通常情况下, Task 需要表示另一个异步操作。 TaskCompletionSource 提供此目的。 它允许创建可以向使用者传递的任务,而这些使用者可以使用任务的成员,就像对待任何其他成员一样。 但是,与大多数任务不同,由创建的任务的状态由 TaskCompletionSource 中的方法显式控制 TaskCompletionSource 。 这使得外部异步操作能够传播到基础 Task。 分隔还可确保使用者不能在不访问相应的的情况下转换状态 TaskCompletionSource。所有成员 TaskCompletionSource 都是线程安全的,可同时从多个线程使用。

3.2 属性函数

属性:

  • Task :获取由此 ‘Task’ 创建的 ‘TaskCompletionSource’。
    此属性使使用者可以访问由此 ‘Task’ 实例控制的。 ‘SetResult()’ ‘SetException(Exception)’ ‘SetException(IEnumerable)’ 此实例上的 和 ‘SetCanceled()’ 方法 (及其 Try 变体) 都将导致相关状态在此基础上转换 Task 。

构造函数:

函数说明
TaskCompletionSource()创建一个 ‘TaskCompletionSource’。
TaskCompletionSource(Object)使用指定的状态创建一个 ‘TaskCompletionSource’。
TaskCompletionSource(Object, TaskCreationOptions)使用指定的状态和选项创建一个 ‘TaskCompletionSource’。'Task’通过此实例创建并可通过其属性访问的将 ‘Task’ 使用指定的实例化 ‘creationOptions’。
TaskCompletionSource(TaskCreationOptions)使用指定的选项创建一个 ‘TaskCompletionSource’。

函数:

PS:基础 Task 已处于以下三种最终状态的其中一种:RanToCompletion、Faulted 或 Canceled。

函数名说明
SetCanceled()将基础 ‘Task’ 转换为 ‘Canceled’ 状态。
SetCanceled(CancellationToken)使用指定的标记将基础 ‘Task’ 转换为 ‘Canceled’ 状态。
SetException(Exception)将基础 ‘Task’ 转换为 ‘Faulted’ 状态。
SetException(IEnumerable)将基础 ‘Task’ 转换为 ‘Faulted’ 状态。
SetResult()将基础 ‘Task’ 转换为 ‘RanToCompletion’ 状态。
TrySetCanceled()尝试将基础 ‘Task’ 转换为 ‘Canceled’ 状态。
TrySetCanceled(CancellationToken)尝试将基础 ‘Task’ 转换为 ‘Canceled’ 状态。
TrySetException(Exception)尝试将基础 ‘Task’ 转换为 ‘Faulted’ 状态。
TrySetException(IEnumerable)尝试将基础 ‘Task’ 转换为 ‘Faulted’ 状态。
TrySetResult()尝试将基础 ‘Task’ 转换为 ‘RanToCompletion’ 状态。

3.3 模拟情景

用户操作:

  • 用户点击商品 --> 选择支付平台 --> 等待选择完成 --> 执行支付逻辑

代码逻辑:

  • 调用支付任务 --> 等待用户选择 --> 返回用户所选 --> 执行支付逻辑
using System;
using System.Threading.Tasks;

namespace VSProject
{
    class Program
    {
        static TaskCompletionSource<string> ts;

        static void Main(string[] args)
        {
            // 开启异步任务
            TaskTest_1();

            while (true)
            {
                ConsoleKeyInfo info = Console.ReadKey(true);
                switch (info.Key)
                {
                    case ConsoleKey.S:
                        Console.WriteLine("用户输入S键,模拟任务成功");
                        Success();
                        break;
                    case ConsoleKey.F:
                        Console.WriteLine("用户输入F键,模拟任务成功");
                        Fail();
                        break;
                }
            }

            Console.ReadLine();
        }

        /// <summary>
        /// 异步任务
        /// </summary>
        /// <returns></returns>
        static async Task TaskTest_1()
        {

            Console.WriteLine("TaskTest_1 任务开始...");
            ts = new TaskCompletionSource<string>();

            string res;
            try
            {
                res = await ts.Task;
            }
            catch (Exception e)
            {
                Console.WriteLine("等待任务异常...");
                throw e;
            }

            Console.WriteLine("TaskTest_1 任务结束... 传值为:" + res);
        }

        /// <summary>
        /// 任务成功
        /// </summary>
        static void Success()
        {
            if (ts.Task.IsCompleted) return;

            ts.SetResult("模拟任务完成传值");           
        }

        /// <summary>
        /// 任务取消或失败
        /// </summary>
        static void Fail()
        {

            if (ts.Task.IsCompleted) return;

            ts.SetException(new Exception("任务取消或失败"));
        }
    }

}

用户按下S键,模拟成功:

用户按下F键,模拟失败:


相关链接:
MSDN - Task 类
MSDN - TaskCompletionSource 类

以上是关于C# 之 多线程 -- 任务概念以及使用示例 ( Task | TaskCompletionSource | Async | Await )的主要内容,如果未能解决你的问题,请参考以下文章

java线程学习

C#多线程之旅——介绍和基本概念

C#多线程之线程池篇3

进程线程服务和任务的区别以及多线程与超线程的概念

多线程之概念

iOS 多线程之GCD的使用