web-dev-qa-db-ja.com

「この非同期メソッドには「await」演算子がなく、同期的に実行される」という警告を心配する必要があります

非同期メソッドを公開するインターフェイスがあります。より具体的には、TaskまたはTask <T>を返すメソッドが定義されています。 async/awaitキーワードを使用しています。

私はこのインターフェースを実装する過程にいます。ただし、これらのメソッドの一部では、この実装には何も待つ必要がありません。そのため、「この非同期メソッドには「await」演算子がなく、同期して実行されます...」というコンパイラー警告が表示されます。

私はなぜエラーを受け取っているのか理解していますが、この文脈でそれらについて何かをすべきかどうか疑問に思っています。コンパイラの警告を無視するのは間違っていると感じています。

Task.Runを待つことで修正できることはわかっていますが、安価な操作を数回しか実行していないメソッドにとっては間違っているように感じます。また、実行に不要なオーバーヘッドが追加されるように聞こえますが、asyncキーワードが存在するため、既に存在するかどうかもわかりません。

警告を無視する必要がありますか、それともこの問題を回避する方法がありますか?

59
dannykay1710

asyncキーワードは、メソッドの実装の詳細にすぎません。メソッドシグネチャの一部ではありません。特定のメソッドの実装またはオーバーライドに待機するものがない場合は、asyncキーワードを省略し、 Task.FromResult <を使用して完了したタスクを返します。 TResult>

_public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }
_

メソッドが Task <TResult> ではなく Task を返す場合、任意のタイプと値の完了したタスクを返すことができます。 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;          //
}                                       //    }
_
102
Michael Liu

一部の「非同期」操作は同期的に完了しますが、ポリモーフィズムのために非同期呼び出しモデルに準拠することは完全に合理的です。

これの実際の例は、OS I/O APIを使用した場合です。一部のデバイスでの非同期呼び出しとオーバーラップ呼び出しは、常にインラインで完了します(たとえば、共有メモリを使用して実装されたパイプへの書き込み)。ただし、バックグラウンドで継続するマルチパート操作と同じインターフェイスを実装します。

10
Ben Voigt

Michael Liuは、Task.FromResultを返すことで、警告を回避する方法に関する質問によく答えました。

あなたの質問の「警告について心配する必要があります」の部分に答えます。

答えはイエスです!

この理由は、Task演算子なしで非同期メソッド内でawaitを返すメソッドを呼び出すと、警告が頻繁に発生するためです。前の操作を待たずにEntity Frameworkで操作を呼び出したために発生した同時実行バグを修正しました。

コンパイラの警告を回避するために細心の注意を払ってコードを記述できる場合、警告があると、ひどい親指のように目立ちます。数時間のデバッグを回避できたはずです。

この警告を回避するためのトリックを見つけました。

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's not used later

    //... your code statements as normal, eg:
    //throw new NotImplementedException();
}

そのawaitキーワードがあると、コンパイラが不正に呼び出され、警告が呼び出されないことがわかっていても警告が表示されなくなります。条件はtrueであるため、常に3項条件(?:)の最初の部分を返します。また、このvarは使用しないvarであるため、リリースビルドでは省略されます。このアプローチに副作用があるかどうかはわかりません。

1
S.Serpooshan

手遅れかもしれませんが、役に立つ調査かもしれません:

コンパイル済みコード(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

これらの方法の大きな違いを見ることができるように。 asyncメソッド内でawaitを使用せず、asyncメソッド(API呼び出しやイベントハンドラーなど)の使用を気にしない場合は、それを通常のsyncメソッドに変換することをお勧めします(アプリケーションのパフォーマンスを節約します)。

0
Oleg Bondarenko