当我在接口方法调用中忘记了“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,所以我很高兴收到警告。
然而,如果我对接口方法进行相同的调用,我不会得到任何警告。在这段代码中,让一个错误溜走是很容易的。
我怎样才能确保我不会犯这个错误?有什么模式我可以用来保护自己吗?
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
)方法中,每当调用返回Task
的async
方法时发出警告,但忽略结果
例如,看看这个(无意义的)代码:
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
。