抑制警告CS1998:此异步方法缺少“await”

137

我有一个接口,其中一些函数返回Task。实现该接口的某些类没有任何需要等待的内容,而其他类可能会抛出异常-因此警告是虚假和烦人的。

有没有可能抑制这些警告?例如:

public async Task<object> test()
{
    throw new NotImplementedException();
}

产生:

警告 CS1998:此异步方法缺少“await”运算符,将同步运行。 请考虑使用“await”运算符等待非阻塞API调用, 或使用“await Task.Run(...)”在后台线程上执行CPU绑定的工作。


1
当在一个被标记为async的函数中不使用新的await关键字时。 - Simon
能否给我们展示一段能重现问题的代码示例? - John Saunders
根据Jamie Marcia的回答,使用#pragma warning disable 1998可以特别回答实际提出的问题...即“是否可能抑制这些警告?” - Simon Sanderson
18个回答

141

我有一个带有一些异步函数的接口。

我相信这些方法返回Task。由于async是一种实现细节,因此不能应用于接口方法。

一些实现该接口的类没有任何东西需要等待,有些可能会抛出异常。

在这些情况下,可以利用async是一种实现细节的特点。

如果你没有要await的内容,那么可以直接返回Task.FromResult

public Task<int> Success() // note: no "async"
{
  ... // non-awaiting code
  int result = ...;
  return Task.FromResult(result);
}

如果抛出NotImplementedException,则流程会更加冗长:

public Task<int> Fail() // note: no "async"
{
  var tcs = new TaskCompletionSource<int>();
  tcs.SetException(new NotImplementedException());
  return tcs.Task;
}

如果你有很多方法会抛出NotImplementedException(这本身可能表明需要进行某些设计层面的重构),那么你可以将冗长的代码封装到一个帮助类中:

public static class TaskConstants<TResult>
{
  static TaskConstants()
  {
    var tcs = new TaskCompletionSource<TResult>();
    tcs.SetException(new NotImplementedException());
    NotImplemented = tcs.Task;
  }

  public static Task<TResult> NotImplemented { get; private set; }
}

public Task<int> Fail() // note: no "async"
{
  return TaskConstants<int>.NotImplemented;
}

这个辅助类还有一个好处是减少了垃圾回收器要收集的无用对象,因为每个返回类型相同的方法都可以共用 TaskNotImplementedException 对象。

我的AsyncEx库中有几个其他的"任务常量"类型示例


2
我没有考虑到关键字的丢失。就像你所说,async与接口无关。我的错,谢谢。 - Simon
3
你能推荐一种返回类型仅为 Task(不带结果)的方法吗? - Mike
13
警告:这种方法可能会导致问题,因为错误不会按照您期望的方式传播。通常调用者会希望在您的方法中出现的异常会在Task中被抛出。相反,您的方法将在它甚至有机会创建一个Task之前就抛出异常。我认为最好的模式是定义一个没有await操作符的异步方法。这样可以确保方法内部的所有代码都被视为Task的一部分。 - Bob Meyers
13
为避免 CS1998 错误,您可以在方法中添加 await Task.FromResult(0);。这样做不会对性能产生显着影响(不像 Task.Yield())。 - Bob Meyers
4
现在你可以直接使用 return Task.CompletedTask; 来完成最简单的任务。 - Stephen Cleary
显示剩余9条评论

82

如果您想保持函数体简单,而不编写支持代码,则另一种选择是使用#pragma来简单地禁止警告:

#pragma warning disable 1998
public async Task<object> Test()
{
    throw new NotImplementedException();
}
#pragma warning restore 1998

如果这种情况很常见,你可以把禁用语句放在文件顶部并省略恢复。

http://msdn.microsoft.com/zh-cn/library/441722ys(v=vs.110).aspx


1
在项目设置中,构建、错误和警告,抑制特定的警告,添加1998。在我看来,相比于在每个地方显式处理任务类型,更容易在任何地方都使用异步写法,仅当接口的某些实现需要等待时再具体处理。 - Alan Baljeu

47

如果您想保留async关键字,另一种方法是使用:

public async Task StartAsync()
{
    await Task.Yield();
}

一旦你填充了该方法,你可以简单地删除该语句。 我经常使用它,特别是当一个方法可能等待某些东西,但并不是每个实现都需要。


这应该是被接受的答案。有时,接口实现不需要是异步的,这比在“Task.Run”调用中包装所有内容要干净得多。 - Andrew Theken
18
等待 Task.CompletedTask 可能是更好的选择。 - Frode Nilsen
@FrodeNilsen 由于某些原因,Task.CompletedTask 看起来不再存在了。 - Sebastián Vansteenkiste
2
@SebastiánVansteenkiste .Net Framework 4.6->,UWP 1.0->,.Net Core 1.0-> - Frode Nilsen
1
@AndrewTheken 我花了一段时间才得出结论,即此答案和您的评论仅适用于实现为空或只抛出异常(与原问题相同)。如果实现确实返回一个值,则似乎 Task.FromResult 是更好的答案。就此而言,如果您确实要抛出异常,似乎已经有另一个回答涉及到 Task.FromException,这使得这永远不是理想的解决方案。您同意吗? - BlueMonkMN

23

解决方案和严格意义上的说法是有区别的,你应该知道调用异步方法的调用者会如何调用,但是使用默认的使用模式假设在方法结果上使用 ".Wait()" - "return Task.CompletedTask" 是最好的解决方案。

    BenchmarkDotNet=v0.10.11, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233537 Hz, Resolution=309.2589 ns, Timer=TSC
.NET Core SDK=2.1.2
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2600.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


         Method |  Job | Runtime |         Mean |       Error |      StdDev |       Median |          Min |          Max | Rank |  Gen 0 |  Gen 1 |  Gen 2 | Allocated |
--------------- |----- |-------- |-------------:|------------:|------------:|-------------:|-------------:|-------------:|-----:|-------:|-------:|-------:|----------:|
 CompletedAwait |  Clr |     Clr |    95.253 ns |   0.7491 ns |   0.6641 ns |    95.100 ns |    94.461 ns |    96.557 ns |    7 | 0.0075 |      - |      - |      24 B |
      Completed |  Clr |     Clr |    12.036 ns |   0.0659 ns |   0.0617 ns |    12.026 ns |    11.931 ns |    12.154 ns |    2 | 0.0076 |      - |      - |      24 B |
         Pragma |  Clr |     Clr |    87.868 ns |   0.3923 ns |   0.3670 ns |    87.789 ns |    87.336 ns |    88.683 ns |    6 | 0.0075 |      - |      - |      24 B |
     FromResult |  Clr |     Clr |   107.009 ns |   0.6671 ns |   0.6240 ns |   107.009 ns |   106.204 ns |   108.247 ns |    8 | 0.0584 |      - |      - |     184 B |
          Yield |  Clr |     Clr | 1,766.843 ns |  26.5216 ns |  24.8083 ns | 1,770.383 ns | 1,705.386 ns | 1,800.653 ns |    9 | 0.0877 | 0.0038 | 0.0019 |     320 B |
 CompletedAwait | Core |    Core |    37.201 ns |   0.1961 ns |   0.1739 ns |    37.227 ns |    36.970 ns |    37.559 ns |    4 | 0.0076 |      - |      - |      24 B |
      Completed | Core |    Core |     9.017 ns |   0.0690 ns |   0.0577 ns |     9.010 ns |     8.925 ns |     9.128 ns |    1 | 0.0076 |      - |      - |      24 B |
         Pragma | Core |    Core |    34.118 ns |   0.4576 ns |   0.4281 ns |    34.259 ns |    33.437 ns |    34.792 ns |    3 | 0.0076 |      - |      - |      24 B |
     FromResult | Core |    Core |    46.953 ns |   1.2728 ns |   1.1905 ns |    46.467 ns |    45.674 ns |    49.868 ns |    5 | 0.0533 |      - |      - |     168 B |
          Yield | Core |    Core | 2,480.980 ns | 199.4416 ns | 575.4347 ns | 2,291.978 ns | 1,810.644 ns | 4,085.196 ns |   10 | 0.0916 |      - |      - |     296 B |

注意:FromResult 不能直接比较。
测试代码:
   [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
   [ClrJob, CoreJob]
   [HtmlExporter, MarkdownExporter]
   [MemoryDiagnoser]
 public class BenchmarkAsyncNotAwaitInterface
 {
string context = "text context";
[Benchmark]
public int CompletedAwait()
{
    var t = new CompletedAwaitTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Completed()
{
    var t = new CompletedTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Pragma()
{
    var t = new PragmaTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Yield()
{
    var t = new YieldTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

    [Benchmark]
    public int FromResult()
    {
        var t = new FromResultTest();
        var t2 = t.DoAsync(context);
        return t2.Result;
    }

public interface ITestInterface
{
    int Length { get; }
    Task DoAsync(string context);
}

class CompletedAwaitTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.CompletedTask;
    }
}

class CompletedTest : ITestInterface
{
    public int Length { get; private set; }
    public Task DoAsync(string context)
    {
        Length = context.Length;
        return Task.CompletedTask;
    }
}

class PragmaTest : ITestInterface
{
    public int Length { get; private set; }
    #pragma warning disable 1998
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        return;
    }
    #pragma warning restore 1998
}

class YieldTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.Yield();
    }
}

    public interface ITestInterface2
    {
        Task<int> DoAsync(string context);
    }

    class FromResultTest : ITestInterface2
    {
        public async Task<int> DoAsync(string context)
        {
            var i = context.Length;
            return await Task.FromResult(i);
        }
    }

}


1
很遗憾,#pragma 看起来会产生开销。可能与返回 CompletedTask 相比,创建并完成 AsyncOperation 会有同样的开销。如果能够告诉编译器在方法同步运行时可以跳过它,那就太好了。 - binki
1
你认为 Task.CompletedTaskTask.FromResult 有多相似?很有趣,我认为 FromResult 是最类似的,如果你需要返回一个值,它仍然是最佳选择。 - BlueMonkMN
我会添加它。我认为在这种情况下状态机代码会更冗长,而CompletedTask将会胜出。让我们看看。 - Roman Pokrovskij
1
希望能够看到.NET Core 2.2的更新,因为异步状态机中的分配已经得到了极大的改善。 - Tseng
1
@Tseng 我已经在.NET Core 2.2.0上运行了基准测试。显然,由于不同的硬件,总时间是不同的,但比率大致相同: 方法 | .NET Core 2.0.3 平均值 | .NET Core 2.2.0 平均值 Completed | 100% | 100% CompletedAwait | 412.57% | 377.22% FromResult | 520.72% | 590.89% Pragma | 378.37% | 346.64% Yield | 27514.47% | 23602.38% - Storm

10
我知道这是一个旧的线程,也许这种方法并不适用于所有情况,但以下方法是我能够找到的最接近的方式,可以在没有改变方法签名的情况下轻松地抛出NotImplementedException,以表示该方法尚未实现。如果这有问题,请告诉我,但对我来说几乎没什么影响:无论如何,我只在开发过程中使用它,因此它的性能并不重要。不过,如果这是个坏主意,我很乐意听听为什么。
public async Task<object> test()
{
    throw await new AwaitableNotImplementedException<object>();
}

这是我添加的类型,使这成为可能。

public class AwaitableNotImplementedException<TResult> : NotImplementedException
{
    public AwaitableNotImplementedException() { }

    public AwaitableNotImplementedException(string message) : base(message) { }

    // This method makes the constructor awaitable.
    public TaskAwaiter<AwaitableNotImplementedException<TResult>> GetAwaiter()
    {
        throw this;
    }
}

9

给大家更新一下Stephen的答案,现在不需要再编写TaskConstants类了,因为有一个新的助手方法:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }

4
不要这样做。堆栈跟踪无法指向您的代码。异常必须被抛出才能完全初始化。 - Daniel B
1
Daniel B - 是的,你说得完全正确。我已经修改了我的答案以正确抛出异常。 - Matt

9
你可以尝试这个:

您可以尝试以下方法:

public async Task<object> test()
{
await Task.CompletedTask; 
}

1
我更喜欢使用 await Task.Delay(0); - alv
@alv,如果我问一下,你为什么更喜欢使用Task.Delay呢? - TheLegendaryCopyCoder
@TheLegendaryCopyCoder 我只会在 真正的测试版 代码上抑制这些 (烦人的) 警告,因此当我接近发布时,我会在代码库中搜索 Task.Delay(0) 并找到这些发生的地方。如果我搜索 Task.CompletedTask,它可能会显示错误的情况,这只是个人偏好,也是一种处理方式 :) - alv
@alv 我明白了,我也会这样做,谢谢。 - TheLegendaryCopyCoder
你的对象怎么样了?使用Return Task.FromResult(object)方法。 - Enrico

3

全局配置/禁用:

在 .editorconfig 文件中

# CS1998: Async method lacks 'await' operators and will run synchronously
dotnet_diagnostic.CS1998.severity = suggestion

在普通的、非高性能的应用程序中,不必要的异步开销是可以忽略不计的,对于普通程序员来说,完全异步化的好处更为重要。 (+附加编译器检查等)

不必要的异步开销可以忽略不计”。这是正确的,但它与你回答的其余部分有什么关系呢?抑制CS1998警告并不是为了接受微不足道的开销,而是为了允许潜在的有害错误被忽视。 - Theodor Zoulias
哎呀,通过同步运行异步方法可能会引入哪些漏洞呢? - yannik
1
不,这是另一个警告:# CS4014:因为此调用未被等待,所以在完成调用之前,当前方法的执行将继续进行。请考虑对调用结果应用“await”运算符。 - yannik
你对结果要做什么?仅凭你的陈述是没有意义的: var result = ProcessAsync(); 是 Task<T> 类型。var result = await ProcessAsync(); 是 T 类型。 此外,如果你声明了一个结果却没有使用它,那就是另一个警告... 试试看;我没有看到任何有效的例子。 - yannik
顺便提一下,你的警告信息是错误的。#CS1998 是关于你所在的方法,而不是你调用的异步方法。如果你的方法中有一个 await,那么就足够了。但是你可以调用 10 个其他的异步方法,无论是否使用 await,都与 #CS1998 无关。 - yannik
显示剩余4条评论

3

如果您已经链接了Reactive Extension,您还可以执行以下操作:

public async Task<object> NotImplemented()
{
    await Observable.Throw(new NotImplementedException(), null as object).ToTask();
}

public async Task<object> SimpleResult()
{
    await Observable.Return(myvalue).ToTask();
}

响应式编程和async/await本身都非常出色,但它们也可以很好地结合在一起。

需要包括:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;

2

这对我不起作用。使用MSBuild.exe构建时,SuppressMessage未抑制CS1998 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接