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

image.png

可以看到,await只会等待当前方法体内,而不影响方法外部

同时也要注意避免在不需要异步操作的方法中使用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阻塞");
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容