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


95

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

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

エラーが発生する理由は理解していますが、このコンテキストでそれらについて何かを行う必要があるかどうか疑問に思っています。コンパイラの警告を無視するのは間違っていると感じます。

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

警告を無視する必要がありますか、それとも私が見ないこれを回避する方法はありますか?


2
それは詳細に依存するでしょう。これらの操作を同期して実行したいのですか?それらを同期的に実行したい場合、メソッドがマークされているのはなぜasyncですか?
Servy 2015

11
asyncキーワードを削除するだけです。Taskを使用して返すことができTask.FromResultます。
Michael Liu

1
@BenVoigt Googleは、OPがまだ知らない場合に備えて、それに関する情報でいっぱいです。
Servy 2015

1
@BenVoigt Michael Liuはすでにそのヒントを提供していませんか?を使用しTask.FromResultます。

1
@hvd:それは後で彼のコメントに編集されました。
Ben Voigt 2015

回答:


147

非同期キーワードは、単にメソッドの実装の詳細です。メソッドシグネチャの一部ではありません。1つの特定のメソッドの実装またはオーバーライドで待機するものがない場合は、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;          //
}                                       //    }

おかげで私が欠けていたのは、あなたが言うようにasyncキーワードを持つのと同じである実際のタスクを返すのではなく、完了したタスクを作成するという概念だったと思います。今は当たり前のようですが、私はそれを見ていませんでした!
dannykay1710 2015

1
Taskは、この目的のためにTask.Emptyの行に沿って静的メンバーを使用して実行できます。意図は少し明確になり、決して必要とされないゼロを返すこれらすべての忠実なタスクについて考えるのは私を苦しめます。
Rupert Rawnsley 2016

await Task.FromResult(0)?どうawait Task.Yield()ですか?
Sushi271 2016年

1
@ Sushi271:いいえ、async方法以外では、待つ代わりに戻っ Task.FromResult(0)てきます。
Michael Liu

1
実際にはありません。非同期は単なる実装の詳細ではなく、注意しなければならない詳細がたくさんあります:)。同期的に実行される部分、非同期的に実行される部分、現在の同期コンテキストは何か、そして記録のために、カーテンの後ろにステートマシンがないため、タスクは常に少し速くなることに注意する必要があります:)。
ipavlu 2017

17

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

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


4

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

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

答えはイエスです!

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

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


5
この答えは間違っています。その理由は次のとおりです。awaitメソッド内に少なくとも1つは1か所に存在する可能性があります(CS1998はありません)が、同期が不足しているasnycメソッドの呼び出しが他にないという意味ではありません(awaitまたはその他を使用)。ここで、誰かが誤って同期を見逃さないようにする方法を知りたい場合は、別の警告(CS4014)を無視しないようにしてください。私はそれをエラーとして脅すことさえお勧めします。
ビクターヤレマ

4

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

コンパイルされたコード(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メソッドに変換することをお勧めします(アプリケーションのパフォーマンスを節約します)。

更新しました:

Microsoft docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depthからの追加情報もあります

asyncメソッドは、本体にawaitキーワードを含める必要があります。そうしないと、生成されません。これは覚えておくことが重要です。非同期メソッドの本体でawaitが使用されていない場合、C#コンパイラは警告を生成しますが、コードは通常のメソッドであるかのようにコンパイルおよび実行されます。asyncメソッド用にC#コンパイラによって生成されたステートマシンは何も実行しないため、これも非常に非効率的であることに注意してください。


2
さらに、async/awaitCPUにバインドされた単一の操作の非現実的な例に基づいているため、の使用に関する最終的な結論は非常に単純化されています。 TaskS使用される場合、適切に改善されたアプリケーションのパフォーマンスと応答による同時タスク(すなわちパラレル)とスレッドのより良い管理および使用を可能にする
MickyD

これは、この投稿で述べたように、テストの簡略化された例です。また、両方のバージョンのメソッド(非同期と通常)を使用して可能な場合は、APIとイベントヘンドラーへのリクエストについても触れました。また、POは、内部で待機せずに非同期メソッドを使用することについて述べました。私の投稿はそれについてでしたが、適切に使用することについてではありませんでしたTasks。あなたが投稿の全文を読んでいて、すぐに結論を出していないのは悲しい話です。
オレグボンダレンコ

2
int(あなたの場合のように)戻るメソッドとTask、OPで説明されているように戻るメソッドには違いがあります。個人的に物事をとる代わりに、彼の投稿と受け入れられた答えをもう一度読んでください。この場合、あなたの答えは役に立ちません。await内部があるメソッドとないメソッドの違いをわざわざ示す必要はありません。今、あなたはそれがあったであろうことを行っていた非常に良いだけでなく価値upvote
MickyD

非同期メソッドと、APIまたはイベントハンドラーで呼び出される通常のメソッドの違いを本当に理解していないと思います。それは私の投稿で特別に言及されました。申し訳ありませんが、もう一度それを見逃しています。
オレグボンダレンコ

1

戻るときの例外動作に関する注意 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.

インターフェイスで非同期タスク<T>が必要な場合、コンパイラの警告なしに戻り変数を取得する方法についての私の回答のクロスポスト)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.