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 gerade 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 verfügt nicht über 'Warten'-Operatoren und wird synchron ausgeführt ..."
Ich verstehe, warum ich den Fehler erhalte, frage mich 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.Run warte, aber das fühlt sich falsch an für eine Methode, die nur wenige 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 ich bin mir auch nicht sicher, ob das 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?
quelle
async
?async
Schlüsselwort. Sie können eineTask
Verwendung weiterhin zurückgebenTask.FromResult
.Task.FromResult
.Antworten:
Das asynchrone Schlüsselwort 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 Schlüsselwort async weg und geben Sie eine abgeschlossene Aufgabe mit Task.FromResult <TResult> zurück :
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 Task.CompletedTask auch zurückgeben :
public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.CompletedTask; // } // }
quelle
await Task.FromResult(0)
? Wie wäre esawait Task.Yield()
?async
Methode kehren Sie zurück,Task.FromResult(0)
anstatt darauf zu warten.Es ist durchaus vernünftig, dass einige "asynchrone" Operationen synchron abgeschlossen werden und dennoch aus Gründen des Polymorphismus dem asynchronen Aufrufmodell entsprechen.
Ein reales Beispiel hierfür sind die OS-E / A-APIs. Asynchrone und überlappende Aufrufe auf einigen Geräten werden immer inline ausgeführt (z. B. Schreiben in eine Pipe, die mithilfe des gemeinsam genutzten Speichers implementiert wurde). Sie implementieren jedoch dieselbe Schnittstelle wie mehrteilige Operationen, die im Hintergrund fortgesetzt werden.
quelle
Es könnte zu spät sein, aber es könnte eine nützliche Untersuchung sein:
Es geht um die innere Struktur von kompiliertem Code ( IL ):
public static async Task<int> GetTestData() { return 12; }
es wird 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 Funktion "Warten innerhalb der asynchronen Methode" nicht verwenden und sich nicht für die Verwendung der asynchronen Methode (z. B. API-Aufruf oder Ereignishandler) interessieren, konvertiert die gute Idee diese in die normale Synchronisierungsmethode (dies spart Ihre Anwendungsleistung).
Aktualisiert:
Weitere Informationen finden Sie in den Microsoft-Dokumenten https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :
quelle
async/await
stark vereinfacht, da Sie sich auf Ihr unrealistisches Beispiel einer einzelnen Operation stützen, die an die CPU gebunden ist.Task
s bei ordnungsgemäßer Verwendung ermöglicht eine verbesserte Anwendungsleistung und Reaktionsfähigkeit aufgrund gleichzeitiger Aufgaben (dh paralleler Aufgaben) und eine bessere Verwaltung und Verwendung von ThreadsTasks
. Es ist eine traurige Geschichte, dass Sie nicht den gesamten Text des Beitrags lesen und schnell Schlussfolgerungen ziehen.int
(wie in Ihrem Fall) zurückgibt, und einer Methode, die zurückgibt, wie imTask
OP beschrieben. Lesen Sie seinen Beitrag und die akzeptierte Antwort erneut, anstatt die Dinge persönlich zu nehmen. Ihre Antwort ist in diesem Fall nicht hilfreich. Sie machen sich nicht einmal die Mühe, den Unterschied zwischen einer Methode zu zeigen, die darin enthalten istawait
oder nicht. Nun hatte man das getan , dass hätte sehr gut lohnt ein upvoteMichael Liu hat Ihre Frage, wie Sie die Warnung vermeiden können, gut beantwortet: indem Sie Task.FromResult zurückgeben.
Ich werde den Teil "Sollte ich mir wegen der Warnung Sorgen machen" Ihrer Frage beantworten.
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 denawait
Operator zurückgegeben wird. Ich habe gerade einen Parallelitätsfehler behoben, der aufgetreten ist, weil ich eine Operation in Entity Framework aufgerufen habe, ohne auf die vorherige Operation zu warten.Wenn Sie Ihren Code sorgfältig schreiben können, um Compiler-Warnungen zu vermeiden, fällt er bei einer Warnung wie ein schmerzender Daumen auf. Ich hätte mehrere Stunden Debuggen vermeiden können.
quelle
await
innerhalb der Methode an einer Stelle geben (es wird keine CS1998 geben), aber dies bedeutet nicht, dass es keinen anderen Aufruf der asnyc-Methode gibt, bei dem die Synchronisation fehlt (usingawait
oder eine andere). Wenn jemand wissen möchte, wie Sie sicherstellen können, dass Sie die Synchronisation nicht versehentlich verpassen, ignorieren Sie einfach eine weitere Warnung - CS4014. Ich würde sogar empfehlen, diesen als Fehler zu bedrohen.Hinweis zum Ausnahmeverhalten bei der Rückkehr
Task.FromResult
Hier ist eine kleine Demo, die den Unterschied in der Ausnahmebehandlung zwischen Methoden zeigt, die markiert und nicht markiert sind
async
.public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!"); // Warning: This async method lacks 'await' operators and will run synchronously. Consider ... public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!"); public string GetToken3Throws() => throw new Exception("Ex3!"); public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws); public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");} public static async Task Main(string[] args) { var p = new Program(); try { var task1 = p.GetToken1WithoutAsync(); } catch( Exception ) { Console.WriteLine("Throws before await.");}; var task2 = p.GetToken2WithAsync(); // Does not throw; try { var token2 = await task2; } catch( Exception ) { Console.WriteLine("Throws on await.");}; var task3 = p.GetToken3WithAsync(); // Does not throw; try { var token3 = await task3; } catch( Exception ) { Console.WriteLine("Throws on await.");}; var task4 = p.GetToken4WithAsync(); // Does not throw; try { var token4 = await task4; } catch( Exception ) { Console.WriteLine("Throws on await.");}; }
// .NETCoreApp,Version=v3.0 Throws before await. Throws on await. Throws on await. Throws on await.
(Cross Post meiner Antwort für Wenn async Task <T> von der Schnittstelle benötigt wird, wie man eine Rückgabevariable ohne Compiler-Warnung erhält )
quelle