Разве вызов .Result для задачи, которая уже ожидалась, нарушает асинхронный шаблон? - PullRequest
0 голосов
/ 12 июня 2019

К тому времени, когда код вызывает Task.Result, он уже ожидался, так что здесь все еще сохраняется асинхронный паттерн?

class Program
{
    static async Task Main(string[] args)
    {
        var addNumbersTask = AddNumbers(10, 20);

        var result = AwaitForResult(addNumbersTask).Result;

        Console.WriteLine(result);
    }

    static async Task<int> AddNumbers(int a, int b)
    {
        await Task.Delay(250);
        return a + b;
    }

    static async Task<int> AwaitForResult(Task<int> task)
    {
        await task;

        return task.Result;
    }
}

Справочная информация, если вам интересно: Попытка передать код IL для прокси-класса, который должен обрабатывать асинхронные вызовы, но я не хочу генерировать асинхронный конечный автомат в IL. Таким образом, я решил, что мог бы делегировать фактическую часть «ожидания» помощнику за пределами IL. Кроме того, я знаю, что существуют прокси-типы, но безнадежный инженер во мне хочет написать это сам.

Редактировать: обновленный пример.

interface IService
{
    Task<int> AddAsync(int a, int b);
}

class Service : IService
{
    public async Task<int> AddAsync(int a, int b)
    {
        await Task.Delay(250);  // Some web service call...
        return a + b;
    }
}

// This class 100% generated via reflection emit
class Proxy : IService
{
    private readonly IService _actual;

    public Proxy(IService actual) => _actual = actual;

    public Task<int> AddAsync(int a, int b)
    {
        return Awaiter.Await(_actual.AddAsync(a, b));
    }
}

static class Awaiter
{
    public static async Task<int> Await(Task<int> task)
    {
        return await task;
    }
}

class Program
{

    static async Task Main(string[] args)
    {
        var proxy = new Proxy(new Service());
        var result = await proxy.AddAsync(5, 5);

        Console.WriteLine($"Result is {result}", result);
    }
}

Ответы [ 3 ]

1 голос
/ 13 июня 2019

Асинхронный шаблон здесь сохраняется?

Нет.Нет ничего волшебного в использовании await для задачи.Вместо этого рассмотрите , завершена ли задача .Вызов task.Result заблокирует вызывающий поток, пока task не завершится.Выполнение await task будет асинхронно ждать, пока не завершится task.

Так что в этом коде Result не будет блокировать:

static async Task<int> AwaitForResult(Task<int> task)
{
  // Task may not yet be complete here.
  await task;
  // At this point, task is complete.

  // Since task is complete, Result does not block.
  return task.Result;
}

Но это полностью отличается от этого кода:

var result = AwaitForResult(addNumbersTask).Result;

// Equivalent to:
var task = AwaitForResult(addNumbersTask);
var result = task.Result;

Задание, возвращаемое с AwaitForResult, может быть не завершено, поскольку оно никогда не было await ред.А если он не завершен, то Result заблокируется.

Попытка передачи кода IL для прокси-класса, который должен обрабатывать асинхронные вызовы, но я не хочу генерировать асинхронный конечный автоматв ил.Поэтому я решил, что мог бы делегировать фактическую часть «ожидание» помощнику за пределами IL.

Пробовали ли вы API-интерфейсы Roslyn?Я нахожу их намного более удобными, чем IL emit.

Если ваш прокси-сервер является просто промежуточным, то вы можете просто вернуть внутреннюю задачу напрямую:

// This class 100% generated via reflection emit
class Proxy : IService
{
  private readonly IService _actual;

  public Proxy(IService actual) => _actual = actual;

  public Task<int> AddAsync(int a, int b) => _actual.AddAsync(a, b);
}

Но если вы хотитедобавьте много реальной логики, тогда я бы рекомендовал использовать Roslyn для генерации конечного автомата async.

0 голосов
/ 12 июня 2019

Метод Awaiter.Await в принципе бессмысленен. Помимо добавления некоторых накладных расходов, возвращаемая задача будет функционально идентична той, что была передана.

Единственная действительная разница в коде между простым возвратом результата _actual.AddAsync(a, b) и ожиданием его состоит в том, что, если AddAsync выдает исключение, если метод async, он возвращает отказавшую задачу вместо броска. Так что, если этого не произойдет, или если Proxy.AddAsync сможет безопасно бросить себя в этой ситуации, просто верните значение, не нужно ничего делать. Если вам нужно убедиться, что Proxy.AddAsync никогда не выдает исключение, а вместо этого возвращает ошибочную задачу, если Proxy.AddAsync выдает исключение, тогда вам просто нужно обернуть метод в try/catch и вернуть ошибочное исключение в блоке catch , Вам не нужно дублировать остальную часть логики конечного автомата async.

Итак, предполагая, что вам нужна та же семантика исключений, что и в поведении метода службы, что, как я подозреваю, вы делаете, вы можете обрабатывать эти методы точно так же, как и синхронный метод, то есть вызывать метод с соответствующим параметры и вернуть результат.

0 голосов
/ 12 июня 2019

Если вы пытаетесь избежать блокировки, то нет.

В вашем примере с консольным приложением это не имеет большого значения, но если вы сделали это где-то, где вы не хотите блокировать - например, обработчик событий пользовательского интерфейса - .Result по-прежнему будет блокироваться.

Когда у вас есть значение в addNumbersTask, эта задача запускается. Затем вы сразу же передаете задачу на AwaitForResult, которая немедленно начинает его ждать, и в этот момент неполное задание, которое возвращает int, возвращается обратно к main(), и затем вы вызываете .Result в теме. Теперь вы блокируете этот поток на протяжении большей части 250 мс, пока эта задача не вернет результат int в .Result в main().

То, как вы выразили это в вопросе, «ожидаемый» не означает «завершенный».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...