IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    C#异步方法返回void和Task的区别

    金庆发表于 2021-02-25 02:38:00
    love 0
    # C#异步方法返回void和Task的区别

    (金庆的专栏 2021.2)

    如果异步(async关键字)方法有返回值,返回类型为T时,返回类型必然是 `Task<T>`。
    但是如果没有返回值,异步方法的返回类型有2种,一个是返回 Task, 一个是返回 void:
    ```
        public async Task CountDownAsync(int count)
        {
            for (int i = count; i >= 0; i--)
            {
                await Task.Delay(1000);
            }
        }

        public async void CountDown(int count)
        {
            for (int i = count; i >= 0; i--)
            {
                await Task.Delay(1000);
            }
        }
    ```

    调用时,如果返回 Task, 但返回值被忽略时,VS 会用绿色波浪线警告:
    ```
        CountDownAsync(3);
        ~~~~~~~~~~~~~~~~~
    ```

    信息为:
    ```
    (awaitable) Task AsyncExample.CountDownAsync(int count)

    Usage:
     await CountDownAsync(...);

    Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
    ```

    中文为:
    ```
    CS4014:由于此调用不会等待,因此在此调用完成之前将会继续执行当前方法。请考虑将"await"运算符应用于调用结果。
    ```

    添加 await 后就正常了:
    ```
        await CountDownAsync(3);
    ```

    如果调用者不是一个异步方法,因为只有在异步方法中才可以使用 await,
    或者并不想在此等待,如想同时执行多个 CountDownAsync(),
    就不能应用 await 来消除警告。

    此时可以改用 void 返回值的版本:
    ```
    void Test()
    {
        ...
        CountDown(3);
        CountDown(3);
        ...
    }

    async void CountDown(int count)
    {
        for (int i = count; i >= 0; i--)
        {
            await Task.Delay(1000);
        }
    }
    ```

    > Never call `async Task` methods without also awaiting on the returned Task. If you don’t want to wait for the async behaviour to complete, you should call an `async void` method instead.

    摘自:http://www.stevevermeulen.com/index.php/2017/09/using-async-await-in-unity3d-2017/

    CountDown() 可以直接调用 CountDownAsync() 实现:
    ```
    async void CountDown(int count)
    {
        await CountDownAsync(count);
    }
    ```

    使用下划线变量忽略异步方法的返回值也可以消除警告:
    ```
    void Test()
    {
        ...
        _ = CountDownAsync(3);
        _ = CountDownAsync(3);
        ...
    }
    ```

    但是这样同时也会忽略 CountDownAsync() 中的异常。如以下异常会被忽略。

    ```
    void Test()
    {
        ...
        _ = CountDownAsync(3);
        ...
    }

    async Task CountDownAsync(int count)
    {
        for (int i = count; i >= 0; i--)
        {
            await Task.Delay(1000);
        }
        throw new Exception();
    }
    ```

    如果是调用返回 void 的异步方法,Unity 会报错:
    ```
    Exception: Exception of type 'System.Exception' was thrown.
    ```

    ## 对 Async 后缀的说明

    ```
    You could say that the Async suffix convention is to communicate to the API user that the method is awaitable. For a method to be awaitable, it must return Task for a void, or Task<T> for a value-returning method, which means only the latter can be suffixed with Async.
    ```

    摘自:https://stackoverflow.com/questions/15951774

    grpc 生成的代码中,异步请求返回了一个 AsyncCall 对象,AsyncCall 实现了 GetAwaiter() 接口:
    ```
          public virtual grpc::AsyncUnaryCall<global::Routeguide.Feature> GetFeatureAsync(global::Routeguide.Point request, ...)
    ```

    可以这样调用并等待:
    ```
        var resp = await client.GetFeatureAsync(req);
    ```

    虽然返回类型不是`Task<>`, 但是可等待,所以添加了 Async 后缀。


    金庆 2021-02-25 10:38 发表评论


沪ICP备19023445号-2号
友情链接