Асинхронно-ожидающие распределения в цепочке вызовов - PullRequest
0 голосов
/ 18 мая 2018

Я пытаюсь лучше понять распределение кучи, которое происходит в результате сгенерированного компилятором кода для асинхронного ожидания в C #.

Рассмотрим следующий код:

static async Task OneAsync()
{
    Console.WriteLine("OneAsync: Start");
    await TwoAsync();
    Console.WriteLine("OneAsync: End");
}

static async Task TwoAsync()
{
    Console.WriteLine("TwoAsync: Start");
    await ThreeAsync();
    Console.WriteLine("TwoAsync: End");
}

static async Task ThreeAsync()
{
    Console.WriteLine("ThreeAsync: Start");
    var c = new HttpClient();
    var content = await c.GetStringAsync("http://google.com");
    Console.WriteLine("Content:" + content.Substring(0, 10));
    Console.WriteLine("ThreeAsync: End");
}

ОтILSpy From ILSpy

Здесь у нас будет 3 AsyncStateMachine структурных типов (один для OneAsync, один для TwoAsync и один для ThreeAsync), сгенерированных компилятором.

Не могли бы вы подтвердить правильность моих предположений?

  • Вызов метода OneAsync (который, в свою очередь, будет вызывать цепочку до ThreeAsync),результат 3 AsyncStateMachine типов структуры для помещения в кучу ?

  • Если я НЕ использовал HttpClient в ThreeAsyncметод и вместо этого просто возвращал Task.CompletedTask из него, тогда было бы 2 AsyncStateMachine типов структуры (один для OneAsync и один для TwoAsync).В этом сценарии было бы НЕТ выделений кучи AsyncStateMachine типов структуры, поскольку вся цепочка вызовов выполняется синхронно?

Ответы [ 2 ]

0 голосов
/ 18 мая 2018

По существу: да на обоих;конечный автомат генерируется, но не попадает в кучу, если он фактически не работает асинхронно.В некоторых критически важных для производительности сценариях, где большая часть вызовов оказывается синхронной, ручная реализация кода для переключения между синхронизацией и асинхронностью может быть выгодна:

static Task OneAsync()
{
    async Task Awaited(Task t)
    {
        await t;
        Console.WriteLine("OneAsync: End");
    }
    Console.WriteLine("OneAsync: Start");
    var task = TwoAsync();
    if (task.Status != TaskStatus.RanToCompletion)
        return Awaited(task);
    Console.WriteLine("OneAsync: End");
    return task; // could also have used Task.CompletedTask
}

Обратите внимание, что для этого требуется некоторое ручное дублирование - вособенности того, что происходит с результатом или после него (Console.WriteLine).Есть способы уменьшить это, часто вовлекая даже больше местных функций.Также обратите внимание, что task.Status необычайно дорог, и, когда он доступен (.NET Core или ValueTask<T>): IsCompletedSuccessfully предпочтительнее.

0 голосов
/ 18 мая 2018

В любом случае у вас будет три конечных автомата, потому что есть три асинхронных метода.Заглушка, созданная компилятором всегда , создает конечный автомат.У вас будет два, если вы измените ThreeAsync, чтобы не иметь модификатора async, и вместо этого просто напишите обычный метод, который вернул завершенное задание.

Вы правыо распределении кучи: все ожидающие ожидающие будут завершены к тому времени, когда их проверят, поэтому не нужно планировать продолжения.Это означает, что нет необходимости выделять что-либо в кучу, кроме (потенциально) задач.Поскольку вы возвращаете обычный Task, и задача всегда будет успешной, я ожидаю, что также будет использоваться кэшированная завершенная задача.

...