Warnung unterdrücken CS1998: Bei dieser asynchronen Methode fehlt das Warten.

104

Ich habe eine Schnittstelle mit einigen asynchronen Funktionen. Einige der Klassen, die die Schnittstelle implementieren, müssen nicht warten, andere werfen möglicherweise nur. Es ist ein bisschen nervig mit all den Warnungen.

Wenn Sie nicht warten, warten Sie in einer asynchronen Funktion.

Ist es möglich, die Nachricht zu unterdrücken?

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

Warnung CS1998: Bei dieser asynchronen Methode fehlen die Operatoren "Warten" und sie wird synchron ausgeführt. Verwenden Sie den Operator 'await', um nicht blockierende API-Aufrufe abzuwarten, oder 'wait Task.Run (...)', um CPU-gebundene Arbeiten an einem Hintergrundthread auszuführen.

Simon
quelle
1
Wenn Sie das Schlüsselwort new await nicht in einer als asynchron gekennzeichneten Funktion verwenden.
Simon
Wie wäre es, wenn Sie uns ein Codebeispiel zeigen, das das Problem reproduziert?
John Saunders

Antworten:

106

Ich habe eine Schnittstelle mit einigen asynchronen Funktionen.

TaskIch glaube, die Methoden kehren zurück . asyncist ein Implementierungsdetail und kann daher nicht auf Schnittstellenmethoden angewendet werden.

Einige der Klassen, die die Schnittstelle implementieren, müssen nicht warten, andere werfen möglicherweise nur.

In diesen Fällen können Sie die Tatsache nutzen, dass asynces sich um ein Implementierungsdetail handelt.

Wenn Sie nichts zu tun haben await, können Sie einfach zurückkehren Task.FromResult:

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

Beim Werfen NotImplementedExceptionist das Verfahren etwas wortreicher:

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

Wenn Sie viele Methoden zum Werfen haben NotImplementedException(was selbst darauf hindeuten könnte, dass ein Refactoring auf Designebene gut wäre), können Sie die Worthaftigkeit in eine Hilfsklasse einschließen:

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

Die Hilfsklasse reduziert auch den Müll, den der GC sonst sammeln müsste, da jede Methode mit demselben Rückgabetyp ihre Taskund NotImplementedExceptionObjekte gemeinsam nutzen kann.

Ich habe mehrere andere Beispiele vom Typ "Task-Konstante" in meiner AsyncEx-Bibliothek .

Stephen Cleary
quelle
1
Ich habe nicht daran gedacht, das Schlüsselwort zu verlieren. Wie Sie sagen, hat Async nichts mit der Schnittstelle zu tun. Mein schlechtes, danke.
Simon
3
Können Sie einen Ansatz empfehlen, bei dem der Rückgabetyp nur Aufgabe ist (ohne Ergebnis?)
Mike
9
Warnung: Dieser Ansatz kann Probleme verursachen, da Fehler nicht wie erwartet weitergegeben werden. Normalerweise erwartet der Anrufer, dass eine Ausnahme in Ihrer Methode innerhalb von auftaucht Task. Stattdessen wird Ihre Methode geworfen, bevor sie überhaupt die Möglichkeit hat, eine zu erstellen Task. Ich denke wirklich, dass das beste Muster darin besteht, eine asyncMethode ohne awaitOperatoren zu definieren . Dies stellt sicher, dass der Code innerhalb der Methode alle als Teil der behandelt wird Task.
Bob Meyers
11
Um CS1998 zu vermeiden, können Sie await Task.FromResult(0);Ihre Methode ergänzen . Dies sollte keine signifikanten Auswirkungen auf die Leistung haben (im Gegensatz zu Task.Yield ()).
Bob Meyers
3
@ AndrewTheken: Heutzutage kannst du es einfach machen return Task.CompletedTask;- das einfachste von allen.
Stephen Cleary
63

Eine andere Option, wenn Sie den Hauptteil der Funktion einfach halten und keinen Code schreiben möchten, um ihn zu unterstützen, besteht darin, die Warnung einfach mit #pragma zu unterdrücken:

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

Wenn dies häufig genug ist, können Sie die disable-Anweisung oben in die Datei einfügen und die Wiederherstellung weglassen.

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

Jamie Macia
quelle
40

Eine andere Möglichkeit, das asynchrone Schlüsselwort beizubehalten (falls Sie es behalten möchten), besteht darin, Folgendes zu verwenden:

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

Sobald Sie die Methode ausgefüllt haben, können Sie die Anweisung einfach entfernen. Ich benutze dies sehr oft, besonders wenn eine Methode auf etwas wartet, aber nicht jede Implementierung dies tatsächlich tut.

Simon Mattes
quelle
Dies sollte die akzeptierte Antwort sein. Manchmal müssen die Schnittstellenimplementierungen nicht asynchron sein. Dies ist viel sauberer, als alles in einen Task.RunAufruf zu packen .
Andrew Theken
12
warte auf Task.CompletedTask; // könnte eine bessere Option sein
Frode Nilsen
@FrodeNilsen scheint aus irgendeinem Grund Task.CompletedTasknicht mehr zu existieren.
Sebastián Vansteenkiste
1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen
1
@AndrewTheken Es hat eine Weile gedauert, bis ich zu dem Schluss gekommen bin, dass diese Antwort und Ihr Kommentar speziell für den Fall gelten, dass die Implementierung leer ist oder nur eine Ausnahme auslöst (wie in der ursprünglichen Frage). Wenn eine Implementierung einen Wert zurückgibt, scheint dies Task.FromResultdie bessere Antwort zu sein. Für diese Angelegenheit, wenn Sie sind eine Ausnahme zu werfen , gehen, scheint es eine andere Antwort ins Spiel kommen , hat sich in Bezug auf Task.FromExceptionHerstellung dieses nie die ideale Lösung. Würdest du zustimmen?
BlueMonkMN
15

Es gibt einen Unterschied zwischen Lösungen und genau genommen sollten Sie wissen, wie der Aufrufer die asynchrone Methode aufrufen wird. Bei einem Standardverwendungsmuster, das ".Wait ()" für das Methodenergebnis voraussetzt, ist " return Task.CompletedTask " die beste Lösung.

    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 |

Hinweis: FromResultKann nicht direkt verglichen werden.

Testcode:

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

}}

Roman Pokrovskij
quelle
1
Es ist bedauerlich, dass der #pragmaeine über Kopf zu entstehen scheint. Wahrscheinlich genauso viel Aufwand, als ob Sie anstelle der Rückgabe eine CompletedTaskerstellt und abgeschlossen hätten AsyncOperation. Es wäre schön, dem Compiler mitteilen zu können, dass es in Ordnung ist, dies zu überspringen, wenn die Methode ohnehin synchron ausgeführt wird.
Binki
Wie ähnlich ist Ihrer Task.CompletedTaskMeinung nach ähnlich Task.FromResult? Es wäre interessant zu wissen - ich gehe davon aus, dass FromResult am analogsten und immer noch am besten abschneidet, wenn Sie einen Wert zurückgeben müssen.
BlueMonkMN
Ich werde es hinzufügen. Ich denke, dass der Code der Zustandsmaschine in diesem Fall ausführlicher sein wird und CompletedTask gewinnen wird. Mal sehen
Roman Pokrovskij
1
Es wäre schön, wenn dies für .NET Core 2.2 aktualisiert würde, da die Zuordnungen in den asynchronen Zustandsautomaten drastisch verbessert wurden
Tseng
1
@Tseng Ich habe die Benchmarks auf .NET Core 2.2.0 ausgeführt. Natürlich ist die Gesamtzeit aufgrund unterschiedlicher Hardware unterschiedlich, aber das Verhältnis bleibt ungefähr gleich: Methode | .NET Core 2.0.3 Mittelwert | .NET Core 2.2.0 Mittelwert abgeschlossen | 100% | 100% abgeschlossenAwarten | 412,57% | 377,22% FromResult | 520,72% | 590,89% Pragma | 378,37% | 346,64% Ausbeute | 27514,47% | 23602,38%
Sturm
10

Ich weiß, dass dies ein alter Thread ist, und vielleicht hat dies nicht für alle Verwendungen den richtigen Effekt, aber das Folgende ist so nah wie möglich daran, einfach eine NotImplementedException auslösen zu können, wenn ich noch keine Methode implementiert habe. ohne die Methodensignatur zu ändern. Wenn es problematisch ist, würde ich mich freuen, es zu wissen, aber es ist mir kaum wichtig: Ich benutze es sowieso nur während der Entwicklung, daher ist es nicht so wichtig, wie es funktioniert. Trotzdem würde ich mich freuen zu hören, warum es eine schlechte Idee ist, wenn es so ist.

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

Hier ist der Typ, den ich hinzugefügt habe, um dies zu ermöglichen.

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;
    }
}
rrreee
quelle
10

Nur als Update für Stephens Antwort müssen Sie die TaskConstantsKlasse nicht mehr schreiben , da es eine neue Hilfsmethode gibt:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Matt
quelle
3
Tu das nicht. Die Stapelverfolgung zeigt nicht auf Ihren Code. Ausnahmen müssen ausgelöst werden, um vollständig initialisiert zu werden.
Daniel B
1
Daniel B - Ja, du hast absolut recht. Ich habe meine Antwort geändert, um die Ausnahme korrekt auszulösen.
Matt
3

Falls Sie bereits eine Verknüpfung zu Reactive Extension herstellen, können Sie außerdem Folgendes tun:

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

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

Reaktiv und asynchron / warten sind an und für sich erstaunlich, aber sie spielen auch gut zusammen.

Eingeschlossen sind:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
John
quelle
3

Es könnte cs1998 unten auftreten.

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

Dann können Sie unten reformieren.

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}
瀧 谷 賢 司
quelle
3

Sie könnten dies versuchen:

public async Task<object> test()
{
await Task.CompletedTask; 
}
Faisal Mehboob
quelle
1

Wenn Sie nichts zu erwarten haben, geben Sie Task.FromResult zurück

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

Hier sind einige Alternativen, die von Ihrer Methodensignatur abhängen.

    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());
    }
Ludde
quelle
-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();
bkoelman
quelle
-2

Sie können das asynchrone Schlüsselwort aus der Methode entfernen und es einfach Task zurückgeben lassen.

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