当我在接口方法调用中忘记了“wait”时,没有任何警告

本文关键字:警告 任何 wait 接口 方法 调用 忘记了 | 更新日期: 2023-09-27 18:25:01

考虑:

using System.Threading.Tasks;
class Program
{
    static void Main(string[] args)
    {
        C c = new C();
        c.FooAsync(); // warning CS4014: 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.
        ((I)c).FooAsync(); // No warning
    }
}
class C : I
{
    public async Task FooAsync()
    {
    }
}
interface I
{
    Task FooAsync();
}

如果我直接在c对象上调用异步方法,我会得到一个编译器警告。这里可能有一个bug,所以我很高兴收到警告。

然而,如果我对接口方法进行相同的调用,我不会得到任何警告。在这段代码中,让一个错误溜走是很容易的。

我怎样才能确保我不会犯这个错误?有什么模式我可以用来保护自己吗?

当我在接口方法调用中忘记了“wait”时,没有任何警告

Main不是异步的,所以它不能使用await。这似乎稍微混淆了编译器消息。如果您将调用放入一个实际的异步方法中;

static void Main(string[] args)
{
    Task.Run(async () =>
                       {
                           C c = new C();
                           c.FooAsync();
                           ((I) c).FooAsync();
                       });
}

两者都会发出警告。

第10行:因为没有等待此调用,所以在调用完成之前,将继续执行当前方法。请考虑将"wait"运算符应用于调用的结果
第11行:因为没有等待此调用,所以在调用完成之前,将继续执行当前方法。请考虑将"wait"运算符应用于调用的结果。

EDIT:似乎所有在异步方法内返回Task的方法都会发出警告,除非您等待或分配它们;注意,我们使用的接口甚至没有提到async;

interface I
{
    Task FooAsync();
}
static void Main(string[] args)
{
    I i = null;
    i.FooAsync();             // Does not warn
    // await i.FooAsync();    // Can't await in a non async method
    var t1 = i.FooAsync();    // Does not warn
    Task.Run(async () =>
    {
       i.FooAsync();          // Warns CS4014
       await i.FooAsync();    // Does not warn
       var t2 = i.FooAsync(); // Does not warn
    });
}

此警告的逻辑似乎是:

  • async方法中,每当调用Task返回方法时发出警告,但忽略结果
  • 在正常(非async)方法中,每当调用返回Taskasync方法时发出警告,但忽略结果

例如,看看这个(无意义的)代码:

Task NonAsyncMethod()
{
    AsyncMethod(); // warnig
    NonAsyncMethod(); // no warning
    return null; // to make the code compile
}
async Task AsyncMethod()
{
    AsyncMethod(); // warning
    NonAsyncMethod(); // warning
}

这就是为什么接口没有得到警告的原因:接口方法没有(也不能)标记为async

我认为这是因为在async之前的旧代码中,通常调用task.ContinueWith()并忽略其结果。如果在这种情况下也报告了警告,那么相对大量的旧的正确代码将突然变成警告。

当出现错误的可能性很大时,应输出警告。我认为报告的案例更有可能是bug,而不是bug。所以对我来说,这种行为是有道理的。

如果您想确保不会犯这个错误,那么在调用Task时要小心——从非async代码返回方法。

我大胆地说,不可能在编译级别发出这个警告。为了支持我的观点,看看这个例子:

interface I
{
    Task Foo();
}
class A : I
{
    public Task Foo()
    {
    }
}
class B : I
{
    public async Task Foo()
    {
    }
}
public class Program
{
    private static void Main(string[] args)
    {
        I i;
        if (Console.ReadLine() == "1")
        {
            i = new A();
        }
        else i = new B();
        i.Foo();
    }
}

你的第一个想法可能是:但这是一个荒谬的情况。但是一些使用机制的设计模式(例如工厂方法)以非常动态的方式实例化派生类。

那么VS如何知道该方法是否是异步的呢?

Lindhart.Analyser.MissingAwaitWarning NuGet包会为您检查此项。在项目中安装nuget包,当方法返回未等待的Task时,会收到编译器警告。

更多信息

我想你可能要求太多了。

interface I
{
    void Foo();
}
class C {} // does not implement I
class Program
{
    static void Main(string[] args)
    {
        C c = new C();
        ((I)c).Foo(); // Generates no compiler warning
    }
}

然而,强制转换发生在运行时,并且在运行时(或在CIL中)没有async这样的内容。编译器将async Task Foo()转换为Task Foo(),实现为协同例程的state-machine