Подтвердить что ты не робот

Должен ли я беспокоиться о "этом асинхронном методе не хватает" ждут "операторов и будет работать синхронно"

У меня есть интерфейс, который предоставляет некоторые асинхронные методы. Более конкретно, он имеет определенные методы, которые возвращают либо задачу, либо задачу <T> . Я использую ключевые слова async/wait.

Я занимаюсь этим интерфейсом. Однако в некоторых из этих методов этой реализации нечего ждать. По этой причине я получаю предупреждение о компиляторе "Этот асинхронный метод не хватает" ждут "операторов и будет работать синхронно..."

Я понимаю, почему я получаю сообщение об ошибке, но мне интересно, должен ли я что-то делать с ними в этом контексте. Неправильно игнорировать предупреждения компилятора.

Я знаю, что могу исправить это, ожидая Task.Run, но это неправильно для метода, который делает только несколько недорогих операций. Похоже, что это добавит ненужные накладные расходы на выполнение, но я также не уверен, что это уже существует, потому что присутствует ключевое слово async.

Должен ли я просто игнорировать предупреждения или есть способ обойти это, что я не вижу?

4b9b3361

Ответ 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 или обработчик событий), хорошая идея преобразует его в обычный метод синхронизации (это сохраняет производительность вашего приложения).