У меня есть интерфейс, который предоставляет некоторые асинхронные методы. В частности, в нем определены методы, возвращающие либо Task, либо Task <T>. Я использую ключевые слова async / await.
Я в процессе реализации этого интерфейса. Однако в некоторых из этих методов этой реализации нечего ждать. По этой причине я получаю предупреждение компилятора: «В этом асинхронном методе отсутствуют операторы await, и он будет работать синхронно ...»
Я понимаю, почему я получаю сообщение об ошибке, но мне интересно, должен ли я что-нибудь с ними делать в этом контексте. Неправильно игнорировать предупреждения компилятора.
Я знаю, что могу исправить это, ожидая выполнения Task.Run, но это неправильно для метода, который выполняет всего несколько недорогих операций. Также похоже, что это добавит ненужные накладные расходы на выполнение, но я также не уверен, есть ли это уже, потому что присутствует ключевое слово async.
Должен ли я просто игнорировать предупреждения или есть способ обойти это, которого я не вижу?
источник
async
?async
ключевое слово. Вы все еще можете вернутьTask
usingTask.FromResult
.Task.FromResult
.Ответы:
Асинхронная ключевое слово является лишь деталью реализации способа; это не часть сигнатуры метода. Если при реализации или переопределении одного конкретного метода нечего ожидать, просто опустите ключевое слово async и верните завершенную задачу с помощью Task.FromResult <TResult> :
public Task<string> Foo() // public async Task<string> Foo() { // { Baz(); // Baz(); return Task.FromResult("Hello"); // return "Hello"; } // }
Если ваш метод возвращает Task вместо Task <TResult> , вы можете вернуть выполненную задачу любого типа и значения.
Task.FromResult(0)
кажется популярным выбором:public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.FromResult(0); // } // }
Или, начиная с .NET Framework 4.6, вы можете вернуть Task.CompletedTask :
public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.CompletedTask; // } // }
источник
await Task.FromResult(0)
? Как насчетawait Task.Yield()
?async
метода вы возвращаетесьTask.FromResult(0)
вместо того, чтобы ждать его.Совершенно разумно, что некоторые «асинхронные» операции выполняются синхронно, но все же соответствуют модели асинхронного вызова ради полиморфизма.
Реальный пример этого - API-интерфейсы ввода-вывода ОС. Асинхронные и перекрывающиеся вызовы на некоторых устройствах всегда завершаются встроенными (например, запись в конвейер, реализованный с использованием общей памяти). Но они реализуют тот же интерфейс, что и операции с несколькими частями, которые продолжаются в фоновом режиме.
источник
Майкл Лю хорошо ответил на ваш вопрос о том, как можно избежать предупреждения: вернув Task.FromResult.
Я собираюсь ответить на часть вашего вопроса «Стоит ли мне беспокоиться о предупреждении».
Ответ - да!
Причина этого в том, что предупреждение часто возникает, когда вы вызываете метод, который возвращается
Task
внутри асинхронного метода безawait
оператора. Я только что исправил ошибку параллелизма, возникшую из-за того, что я вызвал операцию в Entity Framework, не дожидаясь предыдущей операции.Если вы можете тщательно написать свой код, чтобы избежать предупреждений компилятора, то при появлении предупреждения оно будет выделяться, как больной палец. Я мог бы избежать нескольких часов отладки.
источник
await
внутри метода может быть хотя бы один в одном месте (CS1998 не будет), но это не значит, что не будет другого вызова метода asnyc, в котором не будет синхронизации (usingawait
или любой другой). Теперь, если кто-то хочет знать, как убедиться, что вы случайно не пропустите синхронизацию, просто убедитесь, что вы не проигнорируете другое предупреждение - CS4014. Я бы даже рекомендовал угрожать этому как ошибкой.Может быть, уже слишком поздно, но расследование может оказаться полезным:
О внутренней структуре скомпилированного кода ( IL ):
public static async Task<int> GetTestData() { return 12; }
это делается в 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
И без асинхронного метода и метода задач:
public static int GetTestData() { return 12; }
становится:
.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
Как вы могли заметить, большая разница между этими методами. Если вы не используете await внутри асинхронного метода и не заботитесь об использовании асинхронного метода (например, вызов API или обработчик событий), хорошая идея преобразует его в обычный метод синхронизации (это сохраняет производительность вашего приложения).
Обновлено:
Также есть дополнительная информация из документов microsoft https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :
источник
async/await
значительно упрощен, поскольку вы основываете его на своем нереалистичном примере единственной операции, связанной с процессором.Task
s при правильном использовании позволяет повысить производительность иTasks
. Печальная история, что вы не читаете весь текст сообщения и не делаете быстрых выводов.int
(как в вашем случае), и методом, который возвращает,Task
например, как обсуждалось OP. Прочтите его сообщение и принятый ответ еще раз, вместо того, чтобы принимать вещи лично. Ваш ответ в этом случае бесполезен. Вы даже не удосужитесь показать разницу между методом, имеющимсяawait
внутри, или нет. Если бы вы это сделали, это было бы очень хорошо, и за это стоит проголосоватьОбратите внимание на поведение исключения при возврате
Task.FromResult
Вот небольшая демонстрация, которая показывает разницу в обработке исключений между методами, отмеченными и не отмеченными значком
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.
(Перекрестное сообщение моего ответа на вопрос, когда async Task <T> требуется интерфейсом, как получить возвращаемую переменную без предупреждения компилятора )
источник