禁止警告CS1998:此异步方法缺少“等待”


104

我有一些异步功能的接口。一些实现该接口的类没有什么可等待的,而有些则可能会抛出。所有警告都有些令人讨厌。

在异步功能中不使用等待时。

是否可以禁止显示该消息?

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

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


1
在标记为异步的函数中不使用new await关键字时。
西蒙(Simon)

向我们展示重现该问题的代码示例怎么样?
约翰·桑德斯

Answers:


106

我有一些异步功能的接口。

Task我相信方法正在回归。async是一个实现细节,因此不能应用于接口方法。

一些实现该接口的类没有什么可等待的,而有些则可能会抛出。

在这些情况下,您可以利用async作为实现细节的事实。

如果您无所事事await,则可以返回Task.FromResult

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

在throw的情况下NotImplementedException,该过程比较罗word:

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;
}

helper类还减少了GC否则将不得不收集的垃圾,因为具有相同返回类型的每个方法都可以共享其TaskNotImplementedException对象。

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


1
我没有想到失去关键字。如您所说,异步与接口无关。我不好,谢谢。
西蒙(Simon)

3
您能推荐一种返回类型仅是Task(无结果)的方法吗
Mike

9
警告:这种方法可能会引起问题,因为错误不会按照您期望的方式传播。通常情况下,调用方会希望方法中的异常会在中出现Task。取而代之的是,您的方法甚至有机会创建一个Task。我真的认为最好的模式是定义一个async没有await运算符的方法。这样可确保将方法中的代码全部视为的一部分Task
鲍勃·迈耶斯

11
为避免使用CS1998,可以将其添加await Task.FromResult(0);到方法中。这应该不会对性能产生重大影响(与Task.Yield()不同)。
鲍勃·迈耶斯

3
@AndrewTheken:这些天你可以做return Task.CompletedTask;-最简单的。
Stephen Cleary


40

保留async关键字的另一种方法(如果要保留它)是使用:

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

填充方法后,您只需删除语句即可。我经常使用此方法,尤其是当某个方法可能正在等待某事但并非每个实现实际上都在做的时候。


这应该是公认的答案。有时接口实现不需要异步,这比将所有内容包装在Task.Run调用中要干净得多。
Andrew Theken

12
等待Task.CompletedTask; //可能是更好的选择
Frode Nilsen

由于某种原因Task.CompletedTask,@ FrodeNilsen 似乎不再存在。
塞巴斯蒂安·范斯汀基斯特

1
@SebastiánVansteenkiste.Net Framework 4.6->,UWP 1.0-> 、. Net Core 1.0->
Frode Nilsen

1
@AndrewTheken我花了一段时间才得出结论,这个答案和您的评论特别适用于实现为空或仅引发异常的情况(与原始问题相同)。如果实现确实返回了值,则似乎Task.FromResult是更好的答案。对于这个问题,如果你要抛出一个异常,似乎另一个答案已经进入就发挥Task.FromException使这绝不是理想的解决方案。你同意吗?
BlueMonkMN

15

解决方案之间有区别,严格来说,您应该知道调用方将如何调用异步方法,但是默认用法模式假定方法结果上带有.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

您认为Task.CompletedTask与您有多相似Task.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意味着已完成| 100%| 100%完成等待 412.57%| 377.22%FromResult | 520.72%| 590.89%语用| 378.37%| 346.64%收益| 27514.47%| 23602.38%
风暴

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;
    }
}

10

就像对Stephen's Answer的更新一样,您不再需要编写TaskConstants类,因为有一个新的辅助方法:

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

3
不要这样 堆栈跟踪不会指向您的代码。必须抛出异常才能完全初始化。
丹尼尔·B

1
Daniel B-是的,您绝对正确。我已经修改了答案以正确引发异常。
马特

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();
}

反应性和异步/等待两者本身都很棒,但是它们也可以很好地发挥作用。

需要包括:

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

3

它可能在下面的cs1998中发生。

public async Task<object> Foo()
{
    return object;
}

然后,您可以在下面进行改革。

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}



1

如果没有什么可等待的,则返回Task.FromResult

public Task<int> Success() // note: no "async"
{
  ... // Do not have await code
  var result = ...;
  return Task.FromResult(result);
}

1

这是一些替代方法,具体取决于您的方法签名。

    public async Task Test1()
    {
        await Task.CompletedTask;
    }

    public async Task<object> Test2()
    {
        return await Task.FromResult<object>(null);
    }

    public async Task<object> Test3()
    {
        return await Task.FromException<object>(new NotImplementedException());
    }

-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();

-2

您可以从方法中删除async关键字,然后使其返回Task;

    public async Task DoTask()
    {
        State = TaskStates.InProgress;
        await RunTimer();
    }

    public Task RunTimer()
    {
        return new Task(new Action(() =>
        {
            using (var t = new time.Timer(RequiredTime.Milliseconds))
            {
                t.Elapsed += ((x, y) => State = TaskStates.Completed);
                t.Start();
            }
        }));
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.