【问题标题】:Warning CS1998 This async method lacks 'await' operator, correct way to silence?警告 CS1998 此异步方法缺少“等待”运算符,正确的静音方法?
【发布时间】:2020-09-25 10:51:37
【问题描述】:

这是我的测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace asynktest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var t = Test1(true);
            await t; //throws InvalidOperationException here. correct
            t = Test1(false);
            await t; //throws NotImplementedException here. correct

            t = Test2(true); //throws InvalidOperationException here. wrong
            await t;
            t = Test2(false); //throws NotImplementedException here. wrong
            await t;

            t = Test3(true);
            await t; //throws InvalidOperationException here. correct
            t = Test3(false); //throws NotImplementedException here. wrong
            await t;

            t = Test4(true);
            await t; //throws InvalidOperationException here. correct
            t = Test4(false);
            await t; //throws NotImplementedException here. correct

            t = Test5(true);
            await t; //throws InvalidOperationException here. correct
            t = Test5(false);
            await t; //throws NotImplementedException here. correct
        }

        public static async Task<int> Test1(bool err) //CS1998: This async method lacks 'await'
        {
            if (err)
                throw new InvalidOperationException();
            return GetNum(42);
        }

        public static Task<int> Test2(bool err)
        {
            if (err)
                throw new InvalidOperationException();
            return Task.FromResult(GetNum(42));
        }

        public static Task<int> Test3(bool err)
        {
            if (err)
                return Task.FromException<int>(new InvalidOperationException());
            return Task.FromResult(GetNum(42));
        }

        public static async Task<int> Test4(bool err)
        {
            await Task.CompletedTask; // remove CS1998
            if (err)
                throw new InvalidOperationException();
            return GetNum(42);
        }

        public static Task<int> Test5(bool err)
        {
            try
            {
                if (err)
                    return Task.FromException<int>(new InvalidOperationException());
                return Task.FromResult(GetNum(42));
            }
            catch (Exception e)
            {
                return Task.FromException<int>(e);
            }
        }
        public static int GetNum(int num)
        {
            throw new NotImplementedException();
        }
    }
}

Test1 生成我要修复的警告。只有 Test4 和 Test5 不会改变程序流程。但是 Test5 需要大量的样板代码。真的是替代方案是在我的程序中撒上“await Task.CompletedTask”或添加大量愚蠢的Task.FromX代码吗? 在这一点上我真的觉得CS1998是完全错误的,必须沉默。 还是我错过了什么?

编辑:删除 Task 作为结果不是一个选项,它通常是一个接口(我无法控制)实现。

Edit2:程序流程是这里的关键。更改程序以在不同的地方抛出异常并不是一件好事。想象一个程序创建 3 个任务,关闭一个核反应堆,然后是 Task.WaitAll(t1, t2, t3)。保留代码原样或 Test4\Test5,否则反应堆会爆炸:-)

Edit3:我经常读到异步创建不必要的状态机(性能),但从我的示例中您可以看到这并不完全正确。如果没有这个状态机,它将改变程序流程。当然,这必须与强制异步接口实现有关,否则您只会使其同步,我想在这里 CS1998 可能会有所帮助:-) 但是 CS1998 不够聪明,无法理解这是您可以控制的代码还是不是。棘手...

Edit4:我最终忽略了警告,但使用包装器的答案是一个很好的选择。但我希望微软可以制作一个补充的“sync”关键字,除了“async”时生成状态机之外,它还可以自动生成这些包装器。

【问题讨论】:

  • 当它不做任何异步操作时,为什么要将方法声明为async
  • @JohnathanBarclay 这可能只是你必须实现的接口。
  • @WiktorZychla 但不是在这里,如果是,请使用Task.FromResult
  • Edit: removing Task as result is not an option, it is typically an interface (that I cannot control) implementation. Task.FromResult 然后 - 并从函数声明中删除 async。要理解的关键是警告正确地指出你正在做的事情很奇怪。有时奇怪的代码是有效的,当然,但通常它是错误的。

标签: c# async-await


【解决方案1】:

删除任务作为结果不是一个选项,它通常是一个接口(我无法控制)实现

使用async 关键字创建一个状态机,如果实现是同步的,则不需要。

如果方法必须返回Task&lt;TResult&gt; 以满足接口契约,请使用Task.FromResult()

如果方法返回非泛型Task,则返回Task.CompletedTask


在异常处理的情况下,使用Task.FromException

public static Task<int> Test3(bool err)
{
    if (err) return Task.FromException<int>(new InvalidOperationException());
    
    try { return Task.FromResult(GetNum(42)); }
    catch (Exception e) { return Task.FromException<int>(e); }
}

如果样板的数量是一个问题,那么如何使用这些包装器:

Task RunAsTask(Action action)
{
    try { action(); }
    catch (Exception e) { return Task.FromException(e); }
    return Task.CompletedTask;
}

Task<TResult> RunAsTask<TResult>(Func<TResult> func)
{
    try { return Task.FromResult(func()); }
    catch (Exception e) { return Task.FromException<TResult>(e); }
}

然后你可以同步编写你的方法:

public static Task<int> Test1(bool err) => RunAsTask(() =>
{
    if (err)
        throw new InvalidOperationException();
    return GetNum(42);
});

【讨论】:

  • 但正如我的例子所示,它会改变程序的工作方式,并在获取任务而不是执行任务时抛出。想象一下关闭一个核反应堆:创建 3 个任务,执行关闭反应堆,Task.WhenAll(t1, t2, t3)。修复警告后,反应堆爆炸:-)
  • 包装器是一个不错的选择。谢谢。这至少消除了 Test5 作为替代品。包装器之间仍然撕裂,禁用 CS1998 或添加 await Task.CompletedTask。不太确定优点\缺点和“正确”的选择。我感觉 CS1998 只关注性能,但我想这也很重要。
  • async 确实会降低性能,但这对您来说可能不是问题,但是,我会说,纯粹为了更清洁的异常处理而使用async 是对该概念的不当使用。
  • 大多数时候获取任务时抛出可能是可以的,但是由于 Task.FromException 存在,我相信这不是意图。手动编码这种模式很容易出错,而且我实际上也不记得以前见过这种模式。 IMO C# 缺少语言“糖”来涵盖这种情况,例如。一个相应的同步关键字,它会自动生成这个包装器(而不是使用异步时的状态机)。
  • 也许吧,虽然实现一个同步的 Task-returning 方法和复杂的异常处理可能不是一个足够常见的场景来保证语言支持。
猜你喜欢
  • 2012-10-25
  • 2019-08-27
  • 1970-01-01
  • 2019-11-21
  • 2017-04-30
  • 2014-02-13
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多