C#之async和await

一:async

  • 介绍
    async 关键字用于声明一个异步方法。当你在方法签名前使用 async 关键字时,你告诉编译器这个方法内部可能包含异步操作,并且这些方法通常会包含至少一个 await 表达式。async 方法允许你使用 await 关键字来等待异步操作的完成,而不会阻塞调用线程。
  • 方法签名
    async 关键字必须出现在方法返回类型之前,方法名之前。例如:public async Task DoSomethingAsync()。
  • 返回类型
    async 方法通常返回 Task 或 Task<T> 类型,其中 T 是方法的返回类型(如果有的话)。如果方法不需要返回任何值,则可以返回 Task;如果方法需要返回一个值,则可以返回 Task<T>,其中 T 是返回值的类型。此外,从C# 7.0开始,async 方法还可以返回 ValueTask 或 ValueTask<T>,这些类型提供了与 Task 和 Task<T> 类似的功能,但可能具有更低的分配成本。
  • 内部实现
    async 方法内部可以使用 await 关键字来等待异步操作的完成。await 表达式会暂停 async 方法的执行,直到等待的异步操作完成,然后 async 方法会继续执行,从 await 表达式之后的代码开始。
  • 异常处理
    在 async 方法中,任何未处理的异常都会自动封装在返回的 Task 或 Task<T> 对象中。调用方可以通过等待这个 Task 并捕获异常来处理这些异常。
  • 线程
    async 和 await 关键字本身不会创建新线程。它们允许异步操作在后台线程上执行(例如,I/O操作通常会在操作系统提供的线程池线程上异步执行),同时释放调用线程以执行其他任务。
  • 上下文捕获
    默认情况下,await 会捕获当前的同步上下文(例如,UI线程的同步上下文),并在异步操作完成后在该上下文中继续执行。这意味着,如果你在UI线程中调用一个 async 方法,并使用 await 等待异步操作,那么异步操作完成后,控制权将返回到UI线程。然而,你可以通过配置 await 的行为(使用 ConfigureAwait(false))来避免这种上下文捕获,从而提高性能。
  • 命名约定
    虽然这不是强制性的,但通常建议将 async 方法命名为以 Async 结尾,以清楚地表明这些方法是异步的。例如,FetchDataFromWebAsync。

二:await

  • 介绍
    在C#中,await 关键字用于异步编程,它允许你等待一个异步操作(如I/O操作、数据库查询、网络请求等)完成,而不会阻塞当前线程。await 只能在标记为 async 的方法内部使用。
    当你使用 await 关键字时,你实际上是在告诉编译器:“我想等待这个异步操作完成,但我不想阻塞当前线程。请在当前操作完成时,以某种方式继续执行我的代码。”线程的控制权会返回给调用者,允许线程池中的当前线程去处理其他任务。直到任务完成,然后执行会继续在 await 之后的代码。
  • 不会阻塞线程
    await 不会阻塞调用它的线程。相反,它会让出线程的控制权,允许线程继续执行其他任务,直到异步操作完成。
  • 保持上下文
    await 会保留当前的上下文(如同步上下文),但并不保证会保持在同一个线程上执行,并在异步操作完成后返回到该上下文。这意味着,如果你在UI线程中使用 await,异步操作完成后,控制权将返回到UI线程,从而可以安全地更新UI元素。
  • 返回值
    await 关键字用于等待一个 Task 或 Task<TResult> 类型的对象。对于 Task,await 会等待任务完成而不返回任何结果;对于 Task<TResult>,await 会等待任务完成并返回任务的结果。
  • 异常处理
    await 关键字还会处理异步操作中抛出的异常。如果在等待的异步操作中抛出了异常,该异常将在 await 表达式之后被抛出,就像同步代码中的异常一样。

例子1:


image.png

例子2:


image.png

同时也要注意避免在不需要异步操作的方法中使用async和await,因为它们会增加不必要的开销。

为了更好的理解,我写了一个下载图片的例子,并通过打印来深度理解一下他们的用法:

            Task t = TestTaskPoint();
            Debug.WriteLine($"TestTaskPoint这个任务的状态:{t.Status}");
            // 因为TestTaskPoint里用了await task,所以不会阻塞主线程
            Debug.WriteLine("主线程不会被TestTaskPoint阻塞,继续做别的事情");

            Task.Run(() =>
            {
                Thread.Sleep(7000);// 我们等TestTaskPoint全部执行完后看一下状态
                Debug.WriteLine($"TestTaskPoint这个任务的状态:{t.Status}");
            });
 private async Task TestTaskPoint()
 {
     Debug.WriteLine("TestTaskPoint--{0}", Thread.CurrentThread.ManagedThreadId);
     CancellationTokenSource cts222 = new CancellationTokenSource();
     CancellationToken token333 = cts222.Token;
     Task task = Task.Run(async () =>
     {
         for (int i = 0; i < 10; i++)
         {
             token333.ThrowIfCancellationRequested();
             Console.WriteLine($"任务进行中 {i}---{Thread.CurrentThread.ManagedThreadId}");
             await Task.Delay(1000);// 模拟耗时任务,不阻塞线程方式,比如网络下载图片去了
         }
         Console.WriteLine("任务完成.");
     }, token333);


     // 此处用来模拟用户3秒后点击了停止下载图片任务
     await Task.Delay(3000);// 模拟主线程工作耗时,不阻塞线程方式,不影响主线程取响应UI或等的任务
     cts222.Cancel(); // 某个情况下任务被取消,比如这两行代码放在用户点击全部停止按钮事件里

     try
     {
         await task; // 等待任务完成 或 取消 
     }
     catch (OperationCanceledException ex)
     {
         Debug.WriteLine("捕获到异常---任务被取消:{0}--{1}", ex.Message, Thread.CurrentThread.ManagedThreadId);// 比如提示下载图片中途被中断了
     }
     Debug.WriteLine("任务完成或取消后做一些其他事情:{0}",Thread.CurrentThread.ManagedThreadId);// 比如图片全部下载成功提示

 }

来看一下打印:


image.png

最后整理一下:

        private void TestAsync()
        {
            Task t = TestTaskPoint();
            Debug.WriteLine($"TestTaskPoint这个任务的状态:{t.Status}");
            // 因为TestTaskPoint里用了await task,所以不会阻塞主线程
            Debug.WriteLine("主线程不会被TestTaskPoint阻塞,继续做别的事情");

            Task.Run(() =>
            {
                Thread.Sleep(7000);// 我们等TestTaskPoint全部执行完后看一下状态
                Debug.WriteLine($"TestTaskPoint这个任务的状态:{t.Status}");
            });
        }

        private void TestAsync2()
        {
            _ = TestTaskPoint();// 如果不需要TestTaskPoint这个任务的状态,也可以这样写
            // 因为TestTaskPoint里用了await task,所以不会阻塞主线程
            Debug.WriteLine("主线程不会被TestTaskPoint阻塞,继续做别的事情");
        }

        private async void TestAsync3()
        {
            await TestTaskPoint();
            Debug.WriteLine("主线程被TestTaskPoint阻塞");
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容