Блокировка асинхронного метода для незапланированной задачи - PullRequest
1 голос
/ 19 марта 2019

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

private async Task RunAsync(CancellationToken cancel)
{
    bool finished = false;
    while (!cancel.IsCancellationRequested && !finished)
        finished = await FakeTask();
}

private Task<bool> FakeTask()
{
    return Task.FromResult(false);
}

Если я использую этот код без ожидания,В конце концов я блокирую:

// example 1
var task = RunAsync(cancel); // Code blocks here...
... // Other code that could run while RunAsync is doing its thing, but is forced to wait
await task;

// example 2
var task = RunAsync(cancelSource.Token); // Code blocks here...
cancelSource.Cancel(); // Never called

В реальном проекте я фактически не использую FakeTask, и обычно там будет некоторое Task.Delay, которого я жду там, так что код большей части кодавремя фактически не блокируется, или только для ограниченного количества итераций.

Однако в модульном тестировании я использую фиктивный объект, который в значительной степени выполняет то же, что и FakeTask, поэтому, когда я хочу увидетьесли RunAsync реагирует на отмену отмены CancellationToken так, как я этого ожидаю, я застрял.

Я обнаружил, что могу решить эту проблему, добавив, например, await Task.Delay(1) вверху RunAsync, чтобы принудительно вызвать его.чтобы действительно работать асинхронно, но это кажется немного хакерским.Есть ли лучшие альтернативы?

Ответы [ 4 ]

4 голосов
/ 19 марта 2019

У вас неправильная мысленная картина того, что делает await. Значение await:

  • Проверьте, завершен ли ожидаемый объект. Если это так, извлеките его результат и продолжите выполнение сопрограммы.
  • Если он не завершен, зарегистрируйте остаток текущего метода как продолжение ожидаемого и приостановите сопрограмму, вернув управление вызывающей стороне. (Обратите внимание, что это делает полукутину .)

В вашей программе «подделка», ожидаемая, всегда завершена, поэтому никогда не происходит приостановка сопрограммы.

Есть ли лучшие альтернативы?

Если ваша логика потока управления требует, чтобы вы приостановили сопрограмму, используйте Task.Yield.

3 голосов
/ 19 марта 2019

Task.FromResult фактически работает синхронно, как и await Task.Delay(0).Если вы хотите фактически симулировать асинхронный код, позвоните Task.Yield().Это создает ожидаемую задачу, которая асинхронно возвращает к текущему контексту при ожидании.

0 голосов
/ 19 марта 2019

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

В вашем примере нет такой точки, где метод должен ждать результата, поэтому цикл продолжает работать вечно и, таким образом, блокирует вызывающего.

Вставка ожидающего Task.Yield () для имитации некоторой реальной асинхронной работы должна помочь.

0 голосов
/ 19 марта 2019

Как сказал @SLaks, ваш код будет работать синхронно. Одна вещь - это запуск асинхронного кода, а другая - запуск параллельного кода.

Если вам нужно запустить ваш код параллельно, вы можете использовать Task.Run.

class Program
{
    static async Task Main(string[] args)
    {
        var tcs = new CancellationTokenSource();
        var task = Task.Run(() => RunAsync("1", tcs.Token));
        var task2 = Task.Run(() => RunAsync("2", tcs.Token));
        await Task.Delay(1000);
        tcs.Cancel();
        Console.ReadLine();
    }

    private static async Task RunAsync(string source, CancellationToken cancel)
    {
        bool finished = false;
        while (!cancel.IsCancellationRequested && !finished)
            finished = await FakeTask(source);
    }

    private static Task<bool> FakeTask(string source)
    {
        Console.WriteLine(source);
        return Task.FromResult(false);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...