it-swarm.com.de

Sollte ich mir Sorgen machen über die Warnung "Diese asynchrone Methode hat keine 'wait'-Operatoren und wird synchron ausgeführt"?

Ich habe eine Schnittstelle, die einige asynchrone Methoden verfügbar macht. Insbesondere sind Methoden definiert, die entweder Task oder Task <T> zurückgeben. Ich verwende die Schlüsselwörter async/await.

Ich bin dabei, diese Schnittstelle zu implementieren. Bei einigen dieser Methoden hat diese Implementierung jedoch nichts zu erwarten. Aus diesem Grund erhalte ich die Compiler-Warnung "Diese asynchrone Methode hat keine 'wait'-Operatoren und wird synchron ausgeführt ..."

Ich verstehe, warum ich den Fehler erhalte, überlege mir aber, ob ich in diesem Zusammenhang etwas dagegen unternehmen soll. Es fühlt sich falsch an, Compiler-Warnungen zu ignorieren.

Ich weiß, dass ich es beheben kann, indem ich auf einen Task warte. RUN, aber das fühlt sich falsch an für eine Methode, die nur ein paar kostengünstige Operationen ausführt. Es hört sich auch so an, als würde es der Ausführung unnötigen Overhead hinzufügen, aber dann bin ich mir auch nicht sicher, ob dies bereits vorhanden ist, da das Schlüsselwort async vorhanden ist.

Sollte ich die Warnungen einfach ignorieren oder gibt es eine Möglichkeit, dies zu umgehen, die ich nicht sehe?

59
dannykay1710

Das Schlüsselwort async ist lediglich ein Implementierungsdetail einer Methode. Es ist nicht Teil der Methodensignatur. Wenn eine bestimmte Methodenimplementierung oder -überschreibung nichts zu erwarten hat, lassen Sie einfach das asynchrone Schlüsselwort aus und geben Sie eine abgeschlossene Aufgabe mit Task.FromResult <zurück TResult> :

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

Wenn Ihre Methode Task anstelle von Task <TResult> zurückgibt, können Sie eine abgeschlossene Task eines beliebigen Typs und Werts zurückgeben. Task.FromResult(0) scheint eine beliebte Wahl zu sein:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

Ab .NET Framework 4.6 können Sie auch Task.CompletedTask zurückgeben:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }
102
Michael Liu

Es ist durchaus vernünftig, dass einige "asynchrone" Operationen synchron abgeschlossen werden und dennoch dem asynchronen Aufrufmodell aus Gründen des Polymorphismus entsprechen.

Ein reales Beispiel hierfür sind die OS-E/A-APIs. Asynchrone und überlappende Aufrufe werden auf einigen Geräten immer inline ausgeführt (z. B. Schreiben in eine Pipe, die mit Shared Memory implementiert wurde). Sie implementieren jedoch dieselbe Schnittstelle wie mehrteilige Operationen, die im Hintergrund fortgesetzt werden.

10
Ben Voigt

Michael Liu hat Ihre Frage, wie Sie die Warnung umgehen können, gut beantwortet: indem Sie Task.FromResult zurückgeben.

Ich beantworte den Teil Ihrer Frage mit dem Titel "Sollte ich mir wegen der Warnung Sorgen machen".

Die Antwort ist ja!

Der Grund dafür ist, dass die Warnung häufig auftritt, wenn Sie eine Methode aufrufen, die Task innerhalb einer asynchronen Methode ohne den Operator await zurückgibt. Ich habe gerade einen Parallelitätsfehler behoben, der aufgetreten ist, weil ich eine Operation in Entity Framework aufgerufen habe, ohne die vorherige Operation abzuwarten.

Wenn Sie Ihren Code akribisch schreiben können, um Compiler-Warnungen zu vermeiden, wird er bei einer Warnung wie ein schmerzender Daumen auffallen. Ich hätte mehrere Stunden des Debuggens vermeiden können.

Ich habe einen Trick gefunden, um diese Warnung zu umgehen:

public async Task<object> test()
{
    //a pseudo code just to disable the warning about lack of await in async code!
    var xyz = true ? 0 : await Task.FromResult(0); //use a var name that's not used later

    //... your code statements as normal, eg:
    //throw new NotImplementedException();
}

das Vorhandensein dieses wait Schlüsselwortes wird den Compiler betrügen, um die Warnung nicht auszulösen, auch wenn wir wissen, dass es niemals aufgerufen wird! Da die Bedingung true ist, wird immer der erste Teil der ternären Bedingung (? :) zurückgegeben. Da es sich bei dieser Var um eine nicht verwendete Var handelt, wird sie in Release-Builds weggelassen. Ich bin mir nicht sicher, ob dieser Ansatz irgendwelche Nebenwirkungen hat.

1
S.Serpooshan

Es könnte zu spät sein, aber es könnte eine nützliche Untersuchung sein:

Es gibt eine innere Struktur von kompiliertem Code (IL):

 public static async Task<int> GetTestData()
    {
        return 12;
    }

es wird zu in IL:

.method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> 
        GetTestData() cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E   // ..(UsageLibrary.
                                                                                                                                     53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65   // StartType+<GetTe
                                                                                                                                     73 74 44 61 74 61 3E 64 5F 5F 31 00 00 )          // stData>d__1..
  .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  2
  .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0,
           [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1)
  IL_0000:  newobj     instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
  IL_000c:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.m1
  IL_0013:  stfld      int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state'
  IL_0018:  ldloc.0
  IL_0019:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_001e:  stloc.1
  IL_001f:  ldloca.s   V_1
  IL_0021:  ldloca.s   V_0
  IL_0023:  call       instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&)
  IL_0028:  ldloc.0
  IL_0029:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_002e:  call       instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
  IL_0033:  ret
} // end of method StartType::GetTestData

Und ohne Async- und Task-Methode:

 public static int GetTestData()
        {
            return 12;
        }

wird :

.method private hidebysig static int32  GetTestData() cil managed
{
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   12
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method StartType::GetTestData

Wie Sie den großen Unterschied zwischen diesen Methoden sehen konnten. Wenn Sie die asynchrone Methode "wait inside" nicht verwenden und die asynchrone Methode (z. B. API-Aufruf oder Ereignisbehandlungsroutine) nicht verwenden möchten, wird sie von der guten Idee in eine normale Synchronisationsmethode konvertiert (dies schont die Leistung Ihrer Anwendung).

0
Oleg Bondarenko