c# - 非同期処理とは - configureawait




非同期呼び出しを待たないことはまだ非同期ですよね? (2)

これがばかげた質問(または重複)である場合、申し訳ありません。

私は機能 A を持っています:

public async Task<int> A(/* some parameters */)
{
    var result = await SomeOtherFuncAsync(/* some other parameters */);

    return (result);
}

別の関数 BA 呼び出していますが、戻り値を使用していません。

public Task B(/* some parameters */)
{
    var taskA = A(/* parameters */); // #1

    return (taskA);
}

Basync と宣言されておらず、 A の呼び出しを待っていないことに注意してください。 A への呼び出しは、消し忘れの呼び出しではありません BC によって次のように呼び出されます。

public async Task C()
{
    await B(/* parameters */);
}

#1 には await がないことに注意し await 。 私はこれが A の呼び出しを同期 Console.WriteLine 、彼の主張を証明しているように見える Console.WriteLine ログを出し続けていると主張する同僚がいます。

B 内の結果を待たないという理由だけで指摘しましたが、タスクチェーン 待たれ、 A 内のコードの性質 待たないからといって変更されません。 A からの戻り値は必要ないので 、チェーンの誰かがそれを待っている 限り( C で発生する)、呼び出しサイトでタスクを待つ必要はありません。

私の同僚は非常に粘り強く、自分自身を疑い始めました。 私の理解は間違っていますか?


これがばかげた質問ならごめんなさい

それはばかげた質問ではありません。 これは重要な質問です。

私はこれがAの呼び出しを同期化し、彼の主張を証明しているように見えるConsole.WriteLineログを出し続けていると主張する同僚がいます。

それはまさにそこにある根本的な問題であり、あなたは同僚が自分自身や他者を誤解させないようにあなたの同僚を教育する必要があります。 非同期呼び出しのようなものはありません 呼び出し は、 決して 非同期のものではありません。 私と一緒に言ってください。 C#では呼び出しは非同期ではありません 。 C#では、関数を呼び出すと、 すべての引数が計算された直後に その関数が 呼び出され ます。

あなたの同僚やあなたが非同期呼び出しのようなものがあると信じているなら、非同期の仕組みに関するあなたの信念は現実から非常に切り離されるので、あなたは苦痛の世界にいます。

だから、あなたの同僚は正しいですか? もちろんそうです。 すべての関数呼び出しは同期であるため、 A 呼び出しは同期 です。 しかし、「非同期呼び出し」のようなものがあると彼らが信じているという事実は、C#で非同期がどのように機能するかについて彼らがひどく誤解していることを意味します。

特に、同僚が await M() 何らかの形で await M() の呼び出しを「非同期」にすると考えて await M() 場合、同僚は大きな誤解を抱いています。 await 演算子 です。 確かに複雑な演算子ですが、演算子であり、値を操作します。 await M() および var t = M(); await t; await M() var t = M(); await t; var t = M(); await t; 同じこと です。 await は返された値に作用する ため、呼び出し 後に awaitが発生 します await は、コンパイラへの「M()の非同期呼び出しを生成する」などの命令ではあり ません 。 「非同期呼び出し」などはありません。

それが彼らの誤った信念の性質であるなら、あなたは await ことの意味について同僚に教育する機会を持って await ます。 await は、シンプルだが強力な何かを意味します。 その意味は:

  • 私が操作している Task を見てください。
  • タスクが例外的に完了した場合、その例外をスローします
  • タスクが正常に完了した場合、その値を抽出して使用します
  • タスクが不完全な場合、このメソッドの残りを待機中のタスクの継続としてサインアップし、 この呼び出しの 不完全な非同期ワークフローを表す 新しい Task 呼び出し元に 返します。

それが await ているすべてです。 タスクの内容を調べるだけで、タスクが不完全な場合は、「まあ、そのタスクが完了するまでこのワークフローを進めることはできないので、このCPUの何かを見つける呼び出し元に戻るする」。

Aの中のコードの性質は、それを待っていないからといって変わりません。

そのとおりです。 A を同期的に呼び出し、 Task 返します。 呼び出しサイトの後のコードは、 A が戻るまで実行されません。 A の興味深い点は、 A が呼び出し元に不完全な Task を返すことが許可されて おり、そのタスクが 非同期ワークフローのノードを 表して いる ことです。 ワークフローは 既に 非同期であり、ご指摘のとおり、 A が戻った 後の 戻り値での処理 違いはありません。 A は、返された Taskawait かどうかわからない。 A 長く実行され、正常に完了したタスク、例外的に完了したタスク、または不完全なタスクを返します。 しかし、あなたがコールサイトで何もしなくてもそれは変わりません。

Aからの戻り値は必要ないため、呼び出しサイトでタスクを待つ必要はありません。

正しい。

チェーンの上位の誰かがそれを待っている限り(Cで行われます)、呼び出しサイトでタスクを待つ必要はありません。

今、あなたは私を失いました。 なぜ誰も A 返される Task を待た なけれ ばなら ない A ですか? あなたが誤った信念を持っているかもしれないので、誰かがその Taskawait 必要 があると思う理由を言ってください。

私の同僚は非常に粘り強く、自分自身を疑い始めました。 私の理解は間違っていますか?

あなたの同僚はほぼ間違いなく間違っています。 あなたの分析は、すべての Taskawait 必要 があると言っているところまでは正しいように見えますが、これは真実ではありません。 Task await ないのは 奇妙です。 これは、操作を開始したプログラムを作成し、いつ、どのように完了するかを気にしないことを意味し、そのようなプログラムを作成するのは確かに 悪臭が しますが、 要件 はありませんすべての Taskawait 。 もう一度あると信じているなら、その信念が何であるかを言ってください、そして我々はそれを整理します。


あなたが正しいです。 タスクの作成はそれだけを行い、いつ、誰が結果を待つかを気にしません。 await Task.Delay(veryBigNumber);await Task.Delay(veryBigNumber); みてください await Task.Delay(veryBigNumber); SomeOtherFuncAsync で、コンソール出力は期待どおりになるはずです。

これはエリディングと呼ばれ、 このブログ投稿 を読むことをお勧めします。ここでは、なぜそんなことをするべきなのかやるべきではないのかを確認できます。

また、正しいことを証明するコードをコピーする最小限の(少し複雑な)例:

class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine($"Start of main {Thread.CurrentThread.ManagedThreadId}");
            var task = First();
            Console.WriteLine($"Middle of main {Thread.CurrentThread.ManagedThreadId}");
            await task;
            Console.WriteLine($"End of main {Thread.CurrentThread.ManagedThreadId}");
        }

        static Task First()
        {
            return SecondAsync();
        }

        static async Task SecondAsync()
        {
            await ThirdAsync();
        }

        static async Task ThirdAsync()
        {
            Console.WriteLine($"Start of third {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1000);
            Console.WriteLine($"End of third {Thread.CurrentThread.ManagedThreadId}");
        }
    }

これは、実際には非同期であることを証明して End of thirdMiddle of main 書き込みます。 さらに、プログラムの残りの部分とは異なるスレッドで関数の終わりが実行されていることがわかります(ほとんどの場合)。 mainの先頭と中央は両方とも常に同じスレッドで実行されます。これらは実際には同期的であり(mainが起動し、関数チェーンを呼び出し、3番目に戻り( await キーワードで行に戻ることがあります)、両方の関数の await キーワードの後のエンディングは、ThreadPoolのスレッド(または使用している同期コンテキスト)で実行できます。

ここで興味深いのは、 Third Task.Delay がそれほど長くかからず、実際に同期的に終了した場合、これらはすべて単一のスレッドで実行されることです。 さらに、非同期で実行される場合でも、すべてが単一のスレッドで実行される場合 があり ます。 非同期関数が複数のスレッドを使用するというルールはありません。何らかのI / Oタスクが終了するのを待っている間に、他の作業を行うことは非常によくあります。





async-await