C#笔记(异步编程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38801354/article/details/79758425

启动程序时,系统会在内存中创建一个新的进程

进程是构成运行程序的资源的集合,这些资源包括 虚地址空间文件句柄和许多程序运行所需的东西

在进程的内部,系统创建一个称为 线程 的内核对象,它表示真正执行的程序。一旦进程建立,系统会在Main方法的第一行语句中开始线程的执行。

一个进程可能有多个线程,它们将共享进程的资源,系统处理器执行所规划的单位是 线程

1 async/await 特性

异步方法在处理完成之前就返回到调用方法。C#的 async/await 特性可以创建并使用异步方法,该特性有三个部分组成:

  • 调用方法 :该方法调用异步方法,然后再异步方法执行其任务是继续执行
  • 异步方法:该方法异步执行其工作,然后立即返回到调用方法
  • await 表达式用于异步方法内部,指明需要异步执行的任务。一个异步方法可以含有多个 await 表达式(至少一个)

1.1 异步方法

异步方法在完成其工作之前立即返回调用方法,然后再调用方法继续执行的时候完成其工作。

在语法上,异步方法具有如下特点:

  • 方法头中包含 async 方法修饰符,而且必须在返回类型之前,async 关键字只是一个上下文关键字,也就是说它作为方法修饰符(或Lambda表达式修饰符/匿名方法修饰符)之外,还可以作为标识符
  • 包含一个或多个 await 表达式,表示可以异步完成的任务
  • 必须具备以下三种返回类型
    • Task<T>:如果调用方法要从调用中获取一个 T 类型的值,异步方法的返回类型就必须是 Task<T>调用方法可以读取 TaskResult 属性来获取这个 T 类型
    • Task:如果调用方法不需要从异步方法中返回某个值,但需要检查异步方法的状态,那么异步方法可以返回一个 Task 类型的对象
    • void:如果调用方法仅仅想执行异步方法,而不需与它做任何进一步的交互
  • 异步方法的参数可以为任意类型任意数量,但不能为 outref 参数
  • 按照于欸的那个,异步方法的名称应该以 Async 为后缀
  • 除了方法以外,Lambda表达式和匿名方法也可以作为异步对象

1.1.1 异步方法的控制流

异步方法的结构包含三个不同的区域:

  • 第一个 await 表达式之前的部分
  • await 表达式:表示将被异步执行的任务
  • 后续部分:在 await 表达式之后出现的其余代码,包括执行环境,如所在线程小小,目前作用域内的变量值,以及当 await 表达式完成后要重新执行所需的其他消息

由此可看出一个异步方法的控制流

  • 当达到 await 表达式时,异步方法将控制返回调用方法,如果方法的返回类型为 TaskTask<T> 类型,将创建一个 Task 对象,表示需异步完成的任务后续,然后把该 Task 返回到调用方法。
  • await 表达式完成时,执行后续部分(后续部分本身可能含有其他 await 表达式)
  • 后续部分遇到 return 语句或到达方法末尾时,将:
    • 如果方法返回类型为 void,控制流将退出
    • 如果方法返回类型为 Task,后续部分设置 Task 的属性并退出
    • 如果返回类型为 Task<T>,后续部分还将设置 Task 对象的 Result 属性

1.2 await 表达式

await 表达式制定了一个异步执行的任务,由一个 await 关键字和一个空闲对象(称为任务) 组成,这个任务通常为一个 Task 类型的对象。

await task

一个空闲对象即是一个 awaitable 类型的实例,awaitable 类型是指包含 GetAwaiter 方法的类型,该方法没有参数,返回一个称为 awaiter 类型的对象。
awaiter 类型包含以下成员:

  • bool IsCompleted {get;}:是否完成
  • void OCompleted(Action):完成后回调函数
  • void GetResult();
  • T GetResult();T 为任意类型

Task 类,它就是 awaitable 类型,

1.2.1 Task.Run()

尽管BCL中存在很多返回 Task<T> 类型对象的方法,但有时仍可能需要编写自己的方法,作为 await 表达式的任务,这时可以使用 Task.Run 方法来创建一个 Task,关于 Task.Run,有一点很重要那就是:它是在不同的线程上运行你的方法

Task.RunFunc<TReturn> 委托为参数,Func<TReturn> 是一个预定义的委托,它不包含任何参数,返回值的类型为 TReturn,因此要将你的方法传递给 Task.Run 方法,需要给予该方法创建一个委托。而 Task.Run 共有 8个重载,现列出最可能用到的四个委托类型的签名:

  • 委托类型 Action,签名 void Action(),含义:不需参数且无返回值的方法
  • 委托类型 Func<TResult>,签名 TResult Func(),含义:不需参数,返回 TResult 类型对象的方法
  • 委托类型 Func<Task>,签名 Task Func(),含义:不需参数,返回 Task 对象的方法
  • 委托类型 Func<Task<TResult>>,签名 Task<TResult> Func(),含义:不需参数,返回 Task<T> 类型对象的方法

例如:

static class MyClass
{
    public static async Task DoWorkAsync()
    {
        await Task.Run(() => Console.WriteLine(5.ToString())); // Action
        Console.WriteLine((await Task.Run(() => 6)).ToString()); // TResult Func()
        await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString()))); // Task Func()
        int value = await Task.Run(() => Task.Run(() => 8)); // TResult Func()
        Console.WriteLine(value.ToString());
    }

    class Program
    {
        static void Main()
        {
            Task t = MyClass.DoWorkAsync();
            t.Wait();
            Console.WriteLine("Press Enter key to exit");
            Console.Read();
        }
    }
}

以上所有的委托都是无参数需求,如果添加的方法需要参数,则需要创建使用Lambda函数了,形如:
await Task.Run(() => GetSum(5, 6));

1.3 取消异步操作

可以在自定义的异步方法中添加一些特性,使得异步操作可以取消,其中位于 System.Threading.Tasks 命名空间中的两个类: CancellationTokenCancellationTokenSource 就是为此设计。

1.3.1 CancellationTokenSource

CancellationTokenSource 对象可以使用 Token 属性创建可分配到不同任务的 CancellationToken 对象,任何持有 CancellationTokenSource 的对象都可以调用Cancel 方法,这将会使得 CancellationTokenIsCancellationRequested 属性设置为 true

1.3.2 CancellationToken

CancellationToken 对象包含一个任务是否应被取消的信息,拥有 CancellationToken 对象的任务需要定期检查 CancellationToken 对象的 IsCancellationRequested 属性,如果属性值为 true,则任务需停止其操作并返回

例子:

class Program
{
    static void Main()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        MyClass mc = new MyClass();
        Task t = mc.RunAsync(token);
        Thread.Sleep(3000);
        cts.Cancel(); // 取消操作
        t.Wait();
        Console.WriteLine("Was Concelled:{0}", token.IsCancellationRequested);
    }
    class MyClass
    {
        // 引入 CancellationToken 实例作为参数
        public async Task RunAsync(CancellationToken ct)
        {
            // 必须要先检查 IsCancellationRequested 的值
            if (ct.IsCancellationRequested) 
                Return;
            await Task.Run(() => CycleMethod(ct), ct);
        }
        void CycleMethod(CancellationToken ct)
        {
            Console.WriteLine("Starting CycleMethod");
            const int max = 5;
            for (int i=0; i<max; i++)
            {
                // 监控 IsCancellationRequested 的值
                if (ct.IsCancellationRequested)
                    return;
                Thread.Sleep(1000);
                Console.WriteLine("{0} of {1} iterations completed", i+1, max);
            }
        }
    }
}

1.4 调用方法中等待任务

调用方法可以调用任意多个异步方法并接受它们返回的 Task 对象,在某个节点上可能需要等待某个特殊的 Task 对象完成,然后在继续。为此,Task 提供实例方法 Wait,可以在 Task 对象上调用这个方法来等待任务结束。
wait 方法用于单一 Task 对象,也可以使用 WaitAllWaitAny 来等待一组 Task 对象

1.5 异步方法中异步地等待任务

如同调用方法,异步方法关于等待任务也有类似的 Task.WhenAll(taskObject)Task.WhenAny(taskObject) 来实现(程序本来就是默认 Task.WhenAll,即后续部分会等待 await 表达式执行完之后执行)。

1.6 Task.Delay 方法

Task.Delay 方法创建一个 Task 对象,该对象将暂停其在线程中的处理,并在一定时间之后完成,与 Thread.Sleep 阻塞线程不同的是,Task.Delay 不会阻塞线程,线程可以继续处理其他工作。
eg:

await Task.Delay(1000);

1.7 Task.Yield 方法

Task.Yield 方法创建一个立即返回的 awaitable。等待一个 Yield 可以让异步方法在执行后续部分的同时返回到调用方法

猜你喜欢

转载自blog.csdn.net/qq_38801354/article/details/79758425