Должен ли я беспокоиться о "этом асинхронном методе не хватает" ждут "операторов и будет работать синхронно"
У меня есть интерфейс, который предоставляет некоторые асинхронные методы. Более конкретно, он имеет определенные методы, которые возвращают либо задачу, либо задачу <T> . Я использую ключевые слова async/wait.
Я занимаюсь этим интерфейсом. Однако в некоторых из этих методов этой реализации нечего ждать. По этой причине я получаю предупреждение о компиляторе "Этот асинхронный метод не хватает" ждут "операторов и будет работать синхронно..."
Я понимаю, почему я получаю сообщение об ошибке, но мне интересно, должен ли я что-то делать с ними в этом контексте. Неправильно игнорировать предупреждения компилятора.
Я знаю, что могу исправить это, ожидая Task.Run, но это неправильно для метода, который делает только несколько недорогих операций. Похоже, что это добавит ненужные накладные расходы на выполнение, но я также не уверен, что это уже существует, потому что присутствует ключевое слово async.
Должен ли я просто игнорировать предупреждения или есть способ обойти это, что я не вижу?
Ответы
Ответ 1
Ключевое слово async - это просто деталь реализации метода; он не является частью подписи метода. Если какой-либо конкретный метод реализации или переопределить нечего ждать, просто опустите ключевое слово 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; //
} // }
Ответ 2
Совершенно разумно, что некоторые "асинхронные" операции завершаются синхронно, но все же соответствуют модели асинхронного вызова для полиморфизма.
Реальный пример этого - с API-интерфейсами ввода-вывода ОС. Асинхронные и перекрывающиеся вызовы на некоторых устройствах всегда завершаются встроенными (например, запись в канал, реализованный с использованием разделяемой памяти). Но они реализуют тот же интерфейс, что и многочастные операции, которые сохраняются в фоновом режиме.
Ответ 3
Майкл Лю хорошо ответил на ваш вопрос о том, как можно избежать предупреждения: вернув Task.FromResult.
Я собираюсь ответить на часть вашего вопроса "Должен ли я беспокоиться о предупреждении".
Ответ - да!
Причина этого заключается в том, что предупреждение часто возникает, когда вы вызываете метод, который возвращает Task
внутри асинхронного метода без оператора await
. Я только исправил ошибку параллелизма, которая произошла потому, что я вызвал операцию в Entity Framework, не ожидая предыдущей операции.
Если вы можете тщательно написать свой код, чтобы избежать предупреждений компилятора, то при появлении предупреждения он будет выделяться, как больной большой палец. Я мог бы избежать нескольких часов отладки.
Ответ 4
Я нашел способ обойти это предупреждение, обратите внимание, что это не решение, которое предоставляется только для вашей информации (не рекомендуется):
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 not used later
//... your code statements as normal, eg:
//throw new NotImplementedException();
}
существование этого ключевого слова await приведет к тому, что компилятор не выдаст предупреждение, даже если мы знаем, что оно никогда не будет вызвано! так как условием является true
, оно всегда возвращает первую часть троичного условного выражения (? :), а также, поскольку эта переменная является неиспользуемой переменной, она будет опущена в сборках Release. Я не уверен, есть ли какие-либо побочные эффекты с этим подходом.
Ответ 5
Это может быть слишком поздно, но это может быть полезным расследованием:
Существует внутренняя структура скомпилированного кода (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 или обработчик событий), хорошая идея преобразует его в обычный метод синхронизации (это сохраняет производительность вашего приложения).