本文告诉大家 await 的高级用法,包括底层原理。

昨天看到太子写了一段代码,我开始觉得他修改了编译器,要不然下面的代码怎么可以编译通过

await "林德熙逗比";

需要知道,基本可以添加 await 都是可以等待的类型,如 Task 。如果一个类需要可以被等待,那么这个类必须满足以下条件

  • 类里有一个 GetAwaiter 函数

  • GetAwaiter 有返回值,返回值需要继承 INotifyCompletion 并且有 bool IsCompleted { get; },GetResult(),void OnCompleted(Action continuation) 定义

参见:如何实现一个可以用 await 异步等待的 Awaiter - walterlv

但是上面的代码使用的是一个字符串,什么时候可以修改继承字符串?

先让我来说下 await 原理,因为知道了原理,上面的代码实现很简单。看完了本文,你就会知道如何让几乎所有类型包括 int 、string 、自定义的类都支持 await 。

如果真的不想看原理,那么请直接调到文章的最后,看到最后很快就知道如何做。

原理

在 .net 4.5 之后,框架默认提供 async 和 await 的语法糖,这时千万不要认为进入 await 就会进入一个新的线程,实际上不一定会进入一个新的线程才会调用 await 。

那么 await 的语法糖写的是什么?实际上就是以前的 Begin xx 和 End xx 的语法糖。

古时候的写法:

foo.Beginxx();

foo.Endxx(传入委托);

这样大家就无法在一个流程写完,需要分为两个东西,而在 Continus with 下,就需要传入委托。如果委托里又使用了异步,那么又需要传入委托

       task.ContinueWith(_ =>
            {
                Task t1 = new Task(() => { });
                t1.ContinueWith((t2) =>
                {
                    //可以看到如果进入很多的委托
                });
            });

所以这时就使用了 await ,可以让大家按照顺序写。

await task;
Task t1 = new Task(() => { });
await t1;
//可以看到这时不需要进入委托

实际上 await 是在编译时支持的,请看进阶篇:以IL为剑,直指async/await - 布鲁克石 - 博客园

而且千万不要认为 await 一定会进入一个新的线程,实际上他只是把需要写在多处的代码,可以按照流写下载,和写同步代码一样。如果感兴趣 await 不一定会进入一个新的线程请看 There Is No Thread

使用

因为 await 需要找到一个 GetAwaiter 函数,这个函数即使是扩展方法也可以,所以其实上面的代码是这样写的


    public static class KvpbamjhKsvm
    {
        public static HeabdsdnbKevx GetAwaiter(this string str)
        {
            return new HeabdsdnbKevx();
        }
    }

    public class HeabdsdnbKevx : INotifyCompletion
    {
        public bool IsCompleted { get; }

        public void GetResult()
        {
        }

        /// <inheritdoc />
        public void OnCompleted(Action continuation)
        {
        }
    }

HeabdsdnbKevx 就是一个可以等待的类型

现在就可以写出下面的代码

        private static void Main(string[] args)
        {
            DdngSiwchjux();
        }

        private static async void DdngSiwchjux()
        {
            await "林德熙逗比";
        }

当然,上面的这个代码可以运行,不过不会返回什么。下面让我加上一句代码。

        private static void Main(string[] args)
        {
            DdngSiwchjux();
        }

        private static async void DdngSiwchjux()
        {
            await "林德熙逗比";
            Console.WriteLine("csdn");
        }

这时可以看到,Console.WriteLine("csdn");不会运行,因为这时如果在 OnCompleted 函数打断点就可以看到,执行await "林德熙逗比"之后就进入OnCompleted 函数。从上面的原理可以知道,这个函数传入的参数就是两个awaitawait和函数结束之间的代码。如果需要让Console.WriteLine("csdn");运行,那么只需要在OnCompleted运行参数

   public void OnCompleted(Action continuation)
        {
            continuation();
        }

但是作为一个挖坑专业的大神,怎么可以就扩展 string ,下面我把 int 进行扩展

    public static class KvpbamjhKsvm
    {
        public static HeabdsdnbKevx GetAwaiter(this int dxpbnzSce)
        {
            return new HeabdsdnbKevx();
        }
    }

随意写一个值,然后进行等待

现在我准备在 object 加一个扩展方法,所有类型都可以等待,然后把这个扩展方法的 namespace 写为 System ,这样大家就不知道这个是我写的,过了一年我就告诉大家这是 C# 的特性,所有的类都可以等待。但是这个特性需要开光才可以使用,你们直接建的项目没有开光所以没法使用这个特性。

相关博客

使用 Task.Wait()?立刻死锁(deadlock) - walterlv


本文会经常更新,请阅读原文: https://lindexi.gitee.io/lindexi/post/C-await-%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://lindexi.gitee.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系