Вернуть с await при переносе старого асинхронного шаблона в TaskCompletionSource? - PullRequest
0 голосов
/ 18 февраля 2019

Я изучаю шаблон C # Asnc-await и сейчас читаю Параллелизм в C # Cookbook от S. Cleary

Он обсуждает оборачивание старых не TAP-асинхронных шаблонов с помощью TaskCompletionSource (TCS) в конструкции TAP.Чего я не понимаю, так это того, почему он просто возвращает свойство Task объекта TCS, а не ожидает его TCS.Task?

Вот пример кода:

Старый метод для переноса - DownloadString (...):

public interface IMyAsyncHttpService
{
    void DownloadString(Uri address, Action<string, Exception> callback);
}

Включение в конструкцию TAP:

public static Task<string> DownloadStringAsync(
    this IMyAsyncHttpService httpService, Uri address)
{
    var tcs = new TaskCompletionSource<string>();
    httpService.DownloadString(address, (result, exception) =>
    {
        if (exception != null)
            tcs.TrySetException(exception);
        else
            tcs.TrySetResult(result);
    });
    return tcs.Task;
}

Теперь почему бы просто не сделать это так:

public static async Task<string> DownloadStringAsync(
    this IMyAsyncHttpService httpService, Uri address)
{
    var tcs = new TaskCompletionSource<string>();
    httpService.DownloadString(address, (result, exception) =>
    {
        if (exception != null)
            tcs.TrySetException(exception);
        else
            tcs.TrySetResult(result);
    });
    return await tcs.Task;
}

Есть лифункциональная разница между двумя?Второй не более естественный?

Ответы [ 5 ]

0 голосов
/ 19 февраля 2019

Прочтите мой ответ, почему возвращение задачи не очень хорошая идея: Какова цель "возврата в ожидании" в C #?

По сути, вы нарушаете свой стек вызовов, если нет жду жду.

0 голосов
/ 19 февраля 2019

Ребята, спасибо за полезные комментарии.

Тем временем я прочитал источники, на которые есть ссылки, а также исследовал вопрос: под влиянием https://blog.stephencleary.com/2016/12/eliding-async-await.html я пришел к выводу, чтоРекомендуется включать асинхронное ожидание по умолчанию даже в синхронных методах цепочки асинхронных функций и исключать асинхронное ожидание только в тех случаях, когда обстоятельства явно указывают на то, что метод потенциально не будет вести себя иначе, чем ожидается от сценариев асинхронного метода на границе.
Например: синхронный метод является коротким, простым и не имеет внутри операций, которые могут вызвать исключение.Если синхронный метод генерирует исключение, вызывающая сторона получит исключение в вызывающей строке, а не в строке, в которой ожидается задача.Это явно отличается от ожидаемого поведения.

Например, передача параметров вызова на следующий уровень без изменений - это ситуация, в которой imo позволяет опустить async-await.

0 голосов
/ 18 февраля 2019

Есть одно тонкое практическое отличие (кроме версии, в которой await работает медленнее).

В первом примере, если DownloadString выдает исключение (вместо вызова делегата, которому вы передаете его с установленным exception), то это исключение будет всплывать через ваш вызов на DownloadStringAsync.

Во втором случае исключение упаковывается в Task, возвращаемый из DownloadStringAsync.

Итак, предполагая, что DownloadString выдает это исключение (и никаких других исключений не возникает):

Task<string> task;
try
{
    task = httpService.DownloadStringAsync(...);
}
catch (Exception e)
{
    // Catches the exception ONLY in your first non-async example
}

try 
{
    await task;
}
catch (Exception e)
{
    // Catches the exception ONLY in your second async example
}

Возможно, вам нет никакого дела до различия - если вы просто напишите:

await httpService.DownloadStringAsync(...);

, вы не заметите разницу.

Опять же, это происходит только в том случае, еслиDownloadString метод сам кидает.Если вместо этого он вызывает делегата, который вы ему даете с exception, установленным в значение, то между двумя вашими случаями заметной разницы нет.

0 голосов
/ 18 февраля 2019

Отметив его как асинхронный, компилятор сгенерирует предупреждения о том, что следует ожидать этот метод

Вам не нужно отмечать свой собственный метод как async вчтобы получить предупреждение «Задача не ожидала».Следующий код генерирует одно и то же предупреждение для вызовов как T, так и U:

static async Task Main(string[] args)
{
    Console.WriteLine("Done");
    T();
    U();
    Console.WriteLine("Hello");
}

public static Task T()
{
    return Task.CompletedTask;
}

public static async Task U()
{
    await Task.Yield();
    return;
}

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

await - это, как правило, способ указать: «У меня сейчас нет никакой полезной работы, но я будуиметь, когда этот другой Task закончен ", что, конечно, не соответствует действительности (у вас нет другой работы, чтобы сделать позже).Поэтому пропустите await и просто верните то, что вы ожидали.

0 голосов
/ 18 февраля 2019

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

...