Mam interfejs, który udostępnia niektóre metody asynchroniczne. Dokładniej, zdefiniowano metody, które zwracają Task lub Task <T>. Używam słów kluczowych async / await.
Jestem w trakcie wdrażania tego interfejsu. Jednak w przypadku niektórych z tych metod implementacja ta nie ma na co czekać. Z tego powodu otrzymuję ostrzeżenie kompilatora „Ta metoda asynchroniczna nie ma operatorów„ await ”i będzie działać synchronicznie ...”
Rozumiem, dlaczego otrzymuję błąd, ale zastanawiam się, czy powinienem coś z nimi zrobić w tym kontekście. Ignorowanie ostrzeżeń kompilatora wydaje się niewłaściwe.
Wiem, że mogę to naprawić, czekając na Task.Run, ale wydaje się to niewłaściwe w przypadku metody, która wykonuje tylko kilka niedrogich operacji. Wygląda również na to, że doda niepotrzebne obciążenie do wykonania, ale nie jestem również pewien, czy to już istnieje, ponieważ słowo kluczowe async jest obecne.
Powinienem po prostu zignorować ostrzeżenia, czy jest sposób na obejście tego, którego nie widzę?
źródło
async
?async
słowo kluczowe. Nadal możesz zwrócić plikTask
usingTask.FromResult
.Task.FromResult
.Odpowiedzi:
Asynchroniczny kluczowe jest jedynie szczegół wdrożenie metody; nie jest częścią sygnatury metody. Jeśli jedna implementacja lub przesłonięcie metody nie ma nic do czekania, po prostu pomiń słowo kluczowe async i zwróć ukończone zadanie za pomocą Task.FromResult <TResult> :
public Task<string> Foo() // public async Task<string> Foo() { // { Baz(); // Baz(); return Task.FromResult("Hello"); // return "Hello"; } // }
Jeśli Twoja metoda zwraca Task zamiast Task <TResult> , możesz zwrócić ukończone zadanie dowolnego typu i wartości.
Task.FromResult(0)
wydaje się być popularnym wyborem:public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.FromResult(0); // } // }
Lub, od .NET Framework 4.6, możesz zwrócić Task.CompletedTask :
public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.CompletedTask; // } // }
źródło
await Task.FromResult(0)
? A co powieszawait Task.Yield()
?async
sposób, to powrótTask.FromResult(0)
zamiast oczekując go.Jest całkowicie uzasadnione, że niektóre operacje „asynchroniczne” kończą się synchronicznie, ale nadal są zgodne z modelem wywołań asynchronicznych ze względu na polimorfizm.
Przykładem tego w rzeczywistości są interfejsy API we / wy systemu operacyjnego. Asynchroniczne i nakładające się wywołania na niektórych urządzeniach zawsze kończą się w tekście (na przykład zapisywanie do potoku zaimplementowanego przy użyciu pamięci współdzielonej). Ale implementują ten sam interfejs, co operacje wieloczęściowe, które są kontynuowane w tle.
źródło
Michael Liu dobrze odpowiedział na Twoje pytanie, jak uniknąć ostrzeżenia: zwracając Task.FromResult.
Odpowiem na część Twojego pytania „Czy powinienem się martwić o ostrzeżenie”.
Odpowiedź brzmi tak!
Przyczyną tego jest to, że ostrzeżenie często pojawia się, gdy wywołujesz metodę, która zwraca
Task
wewnątrz metody asynchronicznej bez rozszerzeniaawait
operatora. Właśnie naprawiłem błąd współbieżności, który wystąpił, ponieważ wywołałem operację w Entity Framework bez oczekiwania na poprzednią operację.Jeśli potrafisz skrupulatnie napisać kod, aby uniknąć ostrzeżeń kompilatora, to gdy pojawi się ostrzeżenie, będzie ono wyróżniać się jak bolący kciuk. Mogłem uniknąć kilku godzin debugowania.
źródło
await
wewnątrz metody może znajdować się w jednym miejscu (nie będzie CS1998), ale nie oznacza to, że nie będzie innego wywołania metody asnyc, która nie będzie miała synchronizacji (usingawait
lub jakikolwiek inny). Teraz, jeśli ktoś chciałby wiedzieć, jak upewnić się, że przypadkowo nie przegapisz synchronizacji, po prostu nie zignoruj kolejnego ostrzeżenia - CS4014. Poleciłbym nawet zagrozić temu jako błąd.Może być za późno, ale może być przydatne dochodzenie:
Chodzi o wewnętrzną strukturę skompilowanego kodu ( IL ):
public static async Task<int> GetTestData() { return 12; }
staje się w 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
I bez metody asynchronicznej i zadaniowej:
public static int GetTestData() { return 12; }
staje się :
.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
Jak widać, duża różnica między tymi metodami. Jeśli nie używasz await w metodzie async i nie zależy Ci na używaniu metody async (na przykład wywołanie API lub program obsługi zdarzeń), dobrym pomysłem jest przekonwertowanie jej na normalną metodę synchronizacji (oszczędza to wydajność aplikacji).
Zaktualizowano:
Istnieją również dodatkowe informacje z Microsoft Docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :
źródło
async/await
jest znacznie uproszczony, ponieważ opierasz go na nierealistycznym przykładzie pojedynczej operacji związanej z procesorem.Task
Jeśli jest używany prawidłowo, pozwala na lepszą wydajność aplikacji i responsywność dzięki współbieżnym zadaniom (tj. równoległym) oraz lepszemu zarządzaniu i wykorzystaniu wątkówTasks
. To smutna historia, że nie czytasz całego tekstu postu i nie wyciągasz szybko wniosków.int
(jak w twoim przypadku), a metodą zwracającą,Task
taką jak omówiona przez OP. Przeczytaj swój post i zaakceptowanej odpowiedź znowu zamiast podejmowania rzeczy osobiście. Twoja odpowiedź nie jest pomocna w tym przypadku. Nawet nie zadajesz sobie trudu, aby pokazać różnicę między metodą, która maawait
wnętrze lub nie. Gdybyś to zrobił, byłby to bardzo dobry wybórZwróć uwagę na zachowanie wyjątków podczas powrotu
Task.FromResult
Oto małe demo, które pokazuje różnicę w obsłudze wyjątków między metodami oznaczonymi i nieoznaczonymi
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 mojej odpowiedzi dla When async Task <T> wymagane przez interfejs, jak uzyskać zmienną zwracaną bez ostrzeżenia kompilatora )
źródło