6

以下のコードを使用すると、コンソールの「Ready」の前に「Finished」という文字列が表示されると思います。誰かが私に説明できますか、なぜawaitはこのサンプルのタスクの終了を待たないのですか?

    static void Main(string[] args)
    {
        TestAsync();
        Console.WriteLine("Ready!");
        Console.ReadKey();
    }

    private async static void TestAsync()
    {
        await DoSomething();
        Console.WriteLine("Finished");
    }

    private static Task DoSomething()
    {
        var ret = Task.Run(() =>
            {
                for (int i = 1; i < 10; i++)
                {
                    Thread.Sleep(100);
                }
            });
        return ret;
    }
4

3 に答える 3

29

「Ready!」の後に「Finished」が表示される理由 これは、非同期メソッドとの一般的な混乱点のためであり、SynchronizationContextsとは何の関係もありません。彼らはどのスレッドで実行されるかをSynchronizationContextで制御しますが、「async」には順序付けに関して独自の非常に具体的なルールがあります。そうでなければ、プログラムはおかしくなります!:)

'await'は、現在のasyncメソッドの残りのコードが、待機が完了するまで実行されないようにします。発信者については何も約束しません。

非同期メソッドは「void」を返します。これは、元の呼び出し元がメソッドの完了に依存することを許可しない非同期メソッドを対象としています。呼び出し元も待機させたい場合は、非同期メソッドがTask(完了/例外のみを監視する場合)、またはTask<T>実際に値も返したい場合は、を返すようにする必要があります。メソッドの戻り型がこれら2つのいずれかであると宣言すると、コンパイラーが残りの処理を行い、そのメソッド呼び出しを表すタスクを生成します。

例えば:

static void Main(string[] args)
{
    Console.WriteLine("A");

    // in .NET, Main() must be 'void', and the program terminates after
    // Main() returns. Thus we have to do an old fashioned Wait() here.
    OuterAsync().Wait();

    Console.WriteLine("K");
    Console.ReadKey();
}

static async Task OuterAsync()
{
    Console.WriteLine("B");
    await MiddleAsync();
    Console.WriteLine("J");
}

static async Task MiddleAsync()
{
    Console.WriteLine("C");
    await InnerAsync();
    Console.WriteLine("I");
}

static async Task InnerAsync()
{
    Console.WriteLine("D");
    await DoSomething();
    Console.WriteLine("H");
}

private static Task DoSomething()
{
    Console.WriteLine("E");
    return Task.Run(() =>
        {
            Console.WriteLine("F");
            for (int i = 1; i < 10; i++)
            {
                Thread.Sleep(100);
            }
            Console.WriteLine("G");
        });
}

上記のコードでは、「A」から「K」の順に印刷されます。何が起こっているのか:

「A」:他の何かが呼び出される前に

「B」:OuterAsync()が呼び出されていますが、Main()はまだ待機中です。

「C」:MiddleAsync()が呼び出されていますが、OuterAsync()は、MiddleAsync()が完了しているかどうかを確認するために待機しています。

「D」:InnerAsync()が呼び出されていますが、MiddleAsync()は、InnerAsync()が完了しているかどうかを確認するために待機しています。

「E」:DoSomething()が呼び出されていますが、InnerAsync()は、DoSomething()が完了しているかどうかを確認するために待機しています。並行して開始するタスクをすぐに返します。

並列処理のため、InnerAsync()がDoSomething()によって返されるタスクの完全性のテストを終了してから、DoSomething()タスクが実際に開始するまでの間に競合が発生します。

DoSomething()が起動すると、「F」が出力され、1秒間スリープします。

その間、スレッドスケジューリングがめちゃくちゃになっていない限り、InnerAsync()は、DoSomething()がまだ完了していないことをほぼ確実に認識しています。これで非同期マジックが始まります。

InnerAsync()は、それ自体をコールスタックからヤンクし、そのタスクが不完全であることを示します。これにより、MiddleAsync()はそれ自体をコールスタックからヤンクし、それ自体のタスクが不完全であると言います。これにより、OuterAsync()はそれ自体をコールスタックからヤンクし、そのタスクも不完全であると言います。

タスクはMain()に返され、Main()はそれが不完全であることを通知し、Wait()呼び出しが開始されます。

その間...

その並列スレッドで、DoSomething()で作成された古いスタイルのTPLタスクは最終的にスリープを終了します。「G」を印刷します。

そのタスクが完了としてマークされると、残りのInnerAsync()がTPLでスケジュールされて再度実行され、「H」が出力されます。これで、InnerAsync()によって最初に返されたタスクが完了します。

そのタスクが完了とマークされると、残りのMiddleAsync()がTPLでスケジュールされて再度実行され、「I」が出力されます。これで、MiddleAsync()によって最初に返されたタスクが完了します。

そのタスクが完了とマークされると、残りのOuterAsync()がTPLでスケジュールされて再度実行され、「J」が出力されます。これで、OuterAsync()によって最初に返されたタスクが完了します。

OuterAsync()のタスクが完了したので、Wait()呼び出しが返され、Main()は「K」を出力します。

したがって、順序が少し並列化されていても、C#5 asyncは、コンソールの書き込みがその正確な順序で行われることを保証します。

これがまだ混乱しているように思われる場合はお知らせください:)

于 2011-10-26T10:45:47.290 に答える
16

あなたはコンソールアプリを使用しているので、特別なものはありませんSynchronizationContext。つまり、継続はスレッドプールスレッドで実行されます。

TestAsync()また、での呼び出しを待っていませんMain。これは、この行を実行すると次のことを意味します。

await DoSomething();

このTestAsyncメソッドは制御をMainに戻します。これは通常どおり実行を継続します。つまり、「Ready!」を出力します。キーが押されるのを待ちます。

一方、1秒後にDoSomething完了すると、awaitinTestAsyncはスレッドプールスレッドで続行し、「Finished」を出力します。

于 2011-10-25T16:43:16.090 に答える
1

他の人が指摘しているように、コンソールプログラムはデフォルトSynchronizationContextを使用するため、getによって作成された継続awaitはスレッドプールにスケジュールされます。

AsyncContext私のNito.AsyncExライブラリから使用して、単純な非同期コンテキストを提供できます。

static void Main(string[] args)
{
  Nito.AsyncEx.AsyncContext.Run(TestAsync);
  Console.WriteLine("Ready!");
  Console.ReadKey();
}

この関連する質問も参照してください。

于 2011-10-25T17:41:01.643 に答える