5

私は非同期CTPでの最初のプレイから約15分です...(いいですね)。

これが私が一緒にノックした本当にシンプルなサーバーです:

internal class Server
{
    private HttpListener listener;
    public Server()
    {
        listener = new HttpListener();
        listener.Prefixes.Add("http://*:80/asynctest/");
        listener.Start();
        Go();
    }

    async void Go()
    {
        HttpListenerContext context = await listener.GetContextAsync();
        Go();
        using (var httpListenerResponse = context.Response) 
        using (var outputStream = httpListenerResponse.OutputStream) 
        using (var sw = new StreamWriter(outputStream))
        {
            await sw.WriteAsync("hello world");
        }
    }
}

ご覧のとおり、asyncメソッドGoはそれ自体を呼び出します。従来の非同期の世界では、これによりスタックオーバーフローが発生します。これは非同期メソッドには当てはまらないと思いますが、いずれにせよ、確かにしたいと思います。誰?

4

1 に答える 1

13

それをもっと簡単なものに分解しましょう:

async static void Go()
{
    await Something();
    Go();
    await SomethingElse();
}

コンパイラはこれをどのように処理しますか?

基本的に、これは次のスケッチのようなものになります。

class HelperClass
{
    private State state = STARTSTATE;
    public void DoIt()
    {

        if (state == STARTSTATE) goto START;
        if (state == AFTERSOMETHINGSTATE) goto AFTERSOMETHING;
        if (state == AFTERSOMETHINGELSESTATE) goto AFTERSOMETHINGELSE;

        START:
        {
           state = AFTERSOMETHINGSTATE;
           var awaiter = Something().MakeAnAwaiter();
           awaiter.WhenDoneDo(DoIt);
           return;
        }

        AFTERSOMETHING:
        {
           Go();
           state = AFTERSOMETHINGELSESTATE;
           var awaiter = SomethingElse().MakeAnAwaiter();
           awaiter.WhenDoneDo(DoIt);
           return;
        }

        AFTERSOMETHINGELSE:

        return;
    }

    static void Go()
    {
        var helper = new HelperClass();
        helper.DoIt();
    }

ここで覚えておく必要があるのは、各非同期操作が完了すると、「DoIt」がメッセージループによって(もちろんヘルパーの適切なインスタンスで)再度呼び出されるようにスケジュールされていることです。

では、どうなりますか?それを解決します。初めてGoに電話します。それはヘルパーをナンバーワンにし、DoItを呼び出します。それはSomething()を呼び出し、タスクを取り戻し、そのタスクの待機者を作成し、待機者に「完了したら、helper1.DoItを呼び出して」と伝えて戻ります。

10分の1秒後にタスクが完了し、メッセージループがhelper1のDoItを呼び出します。helper1の状態はAFTERSOMETHINGSTATEであるため、gotoを取得してGoを呼び出します。それはhelper2を作成し、その上でDoItを呼び出します。これにより、Something()が呼び出され、タスクが返され、そのタスクの待機者が作成され、待機者に「完了したら、helper2でDoItを呼び出します」と通知され、helper1のDoItに制御が戻ります。これはSomethingElseを呼び出し、そのタスクを待機し、「他のことを行ったら、helper1のDoItを呼び出します」と伝えます。その後、戻ります。

これで、2つのタスクが未解決になり、スタックにコードがありません。タスクの1つが最初に完了します。SomethingElseタスクが最初に完了したとします。メッセージループはhelper1.DoIt()を呼び出し、すぐに戻ります。Helper1はガベージになりました。

その後、メッセージループはhelper2.DoIt()を呼び出し、AFTERSOMETHINGに分岐します。ここでGo()が呼び出され、helper3...が作成されます。

いいえ、ここには無制限の再帰はありません。Goが実行されるたびに、Something()を非同期的に開始するまで実行され、呼び出し元に戻ります。「何か」の後のものへの呼び出しは後で起こります。「Go」は一度に一度だけスタックにあります。

于 2011-06-23T03:24:07.153 に答える