асинхронная CTP-рекурсия - PullRequest
5 голосов
/ 23 июня 2011

У меня около 15 минут на мою первую игру с асинхронным CTP ... (хорошо).

Вот действительно простой сервер, который я собрал вместе:

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");
        }
    }
}

Как видно, асинхронный метод Go вызывает сам себя.В классическом не асинхронном мире это может вызвать переполнение стека.Я предполагаю, что это не относится к асинхронному методу, но я хотел бы быть уверен, так или иначе.Кто-нибудь?

1 Ответ

13 голосов
/ 23 июня 2011

Давайте разберем это на что-то более простое:

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» и возвращает.

Десятая секунда спустя задача завершается, и цикл сообщений вызывает DoIt helper1. Состояние helper1 - AFTERSOMETHINGSTATE, поэтому мы берем goto и вызываем Go. Это делает helper2 и вызывает DoIt на этом. Это вызывает Something (), возвращает задачу назад, делает ее ожидающей, сообщает ожидающему «когда вы закончите, вызовите DoIt для helper2» и возвращает управление обратно в DoIt helper1. Это вызывает SomethingElse, делает ожидание для этой задачи и говорит ему "когда вы закончите делать что-то еще, вызовите DoIt helper1". Затем он возвращается.

Теперь у нас есть две нерешенные задачи и нет кода в стеке. Одна из задач будет выполнена в первую очередь. Предположим, что задача SomethingElse завершается первой. Цикл сообщений вызывает helper1.DoIt (), который немедленно возвращается. Helper1 теперь фигня.

Позже цикл сообщений вызывает helper2.DoIt () и переходит к AFTERSOMETHING. Теперь вызывается Go (), который создает helper3 ...

Так что нет, здесь нет неограниченной рекурсии. Каждый раз, когда Go запускается, он запускается до асинхронного запуска Something () и затем возвращается к своему вызывающему. Призыв к вещам после «чего-то» происходит позже. «Go» находится в стеке только один раз за раз.

...