Асинхронная и синхронная версии метода - PullRequest
0 голосов
/ 18 января 2019

Хорошо, поэтому я много читал и работаю над лучшими способами использования async methods и task и т. Д. Я полагаю, что (в основном) это понимаю, но я хочу проверить, чтобы убедиться.

Я начал делать async wrappers для задач синхронизации, используя Task.Run(), но недавно прочитал, что гораздо лучше просто иметь sync method и позволить приложению определить, нужно ли его передавать в другой поток с помощью Task.Run() сам, что имеет смысл.Тем не менее, я также читал, что исключение для этого естественно async functions.Я не уверен, что полностью понимаю «естественно асинхронный», но в качестве базового понимания кажется, что методы платформы .NET, которые предлагают async methods, являются naturally async и WebClient.DownloadFile/DownlodFileAsync - один из них.

Так что яу меня есть два метода, и если кто-то захочет высказать свое мнение, я хотел бы проверить свое понимание и убедиться, что это правильно.

Метод первый - перемещение некоторых файлов в локальной операционной системе и выглядит следующим образом (псевдокод):

Public static void MoveStagingToCache()
{
    ...do some file move, copy, delete operations...
}

Второй выглядит так (псевдокод):

Public static void DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        wc.DownloadFile(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

Итак, мое понимание таково.Первый метод должен быть оставлен как есть, поскольку ни один из методов файловой операции не имеет async versions и, следовательно, вряд ли естественно async.Вызывающий может определить, нужно ли просто вызывать MoveStagingToCache() для запуска синхронизации или вызывать Task.Run(()=>MoveStagingToCache()) для передачи его в фоновый поток.

Однако во втором методе загрузка, естественно, async (если я не понимаю), чтобы он мог создать синхронизацию и асинхронную версию.Чтобы сделать это, я НЕ должен просто обернуть метод синхронизации следующим образом:

Public static Task DownloadToCacheAsync()
{
    return Task.Run(()=>DownloadToCache());
}

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

Public static async Task DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        await wc.DownloadFileTaskAsync(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

, и тогда я смогусоздайте версию синхронизации следующим образом:

Public static void DownloadToCache()
{
    DownloadToCacheAsync().Wait();
}

Это позволяет использовать естественный асинхронный метод, а также предлагает перегрузку синхронизации для тех, кто в ней нуждается.

Это хорошее пониманиесистема или я что-то напутал?

Кроме того, есть ли разница в WebClient.DownloadFileAsync и WebClient.DownloadFileTaskAsync только в том, что задача возвращает задачу для обнаружения ошибок?

РЕДАКТИРОВАТЬ

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

Для MoveStagingToCache это будет вызываться при запуске программы, но мне не нужно ждать завершения, чтобы продолжить загрузку или использование программы.В большинстве случаев она завершится до запуска и запуска остальной части программы и позволит пользователю сделать что-либо наиболее вероятное (максимум 5-10 секунд времени выполнения), но даже если оно не завершено до запуска взаимодействия с пользователем, программа будет работать нормально,Из-за этого мое основное желание здесь состоит в том, чтобы переместить эту операцию из потока пользовательского интерфейса, запустить ее и продолжить работу с программой.

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

Task.Run(()=>MoveStagingToCache());

Поскольку мне не нужно ничего делать по завершении, мне действительно не нужно ждать этогоправо?Если я просто сделаю выше, он начнет операцию в фоновом потоке?

Для DownloadToCache, похоже, но немного по-другому. Я хочу, чтобы пользователь мог инициировать операцию загрузки, а затем продолжать работу в пользовательском интерфейсе до завершения загрузки. После его завершения мне нужно будет выполнить несколько простых операций, чтобы уведомить пользователя, что он готов пойти и включить кнопку «использовать» и т. Д. В этом случае я понимаю, что я бы создал это как асинхронный метод, который ожидает WebClient скачать звонок. Это вытолкнет его из потока пользовательского интерфейса для фактической загрузки, но затем вернется после завершения загрузки, чтобы позволить мне делать все необходимые обновления пользовательского интерфейса после вызова await.

Правильно?

Ответы [ 2 ]

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

Вы не должны писать асинхронные оболочки для синхронных методов , но вы также не должны писать синхронные оболочки для асинхронных методов - это и антипаттерны.

Подсказка: "естественно асинхронный" в основном означает на основе ввода / вывода, за некоторыми исключениями. Одним из таких исключений, к сожалению, являются некоторые операции с файловой системой, в том числе перемещение файлов, которые должны быть асинхронными, но API не поддерживают асинхронность, поэтому мы должны притвориться, что она синхронная.

В вашем случае DownloadToCache определенно является асинхронным. В этих случаях я предпочитаю выставлять только асинхронный API .

Если вы должны (или действительно хотите) также поддерживать синхронный API, я рекомендую взломать логический аргумент . Смысл в том, что если вы передадите sync:true, то возвращаемое задание уже выполнено Это позволяет вам сохранять логику в одном методе и писать очень маленькие оболочки без ошибок, обычно связанных с этими оболочками:

private static async Task DownloadToCacheAsync(bool sync)
{
  ...do some analysis to get download locations...
  using (var wc = new WebClient())
  {
    if (sync)
      wc.DownloadFile(new Uri(content.Url), targetPath);
    else
      await wc.DownloadFileTaskAsync(new Uri(content.Url), targetPath);
  }
  ...do other stuff...
}
public static Task DownloadToCacheAsync() => DownloadToTaskAsync(sync: false);
public static void DownloadToCache() => DownloadToTaskAsync(sync: true).GetAwaiter().GetResult();
0 голосов
/ 19 января 2019

Вы задали несколько взаимосвязанных вопросов, позвольте мне попытаться ответить на большинство из них, для начала прочитайте статьи Стивена Клири для большей ясности в работе Async, особенно в отношении Естественно, Async - нет потока , это поможет понять, что означает естественный метод Async.

Асинхронные операции

Есть два вида Async operations IO bound and CPU bound

Как работает асинхронное связывание с IO

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

Что происходит при возврате асинхронного вызова IO

Когда вызов возвращается, любой рабочий поток может быть назначен для завершения вызова, что можно указать, используя ConfigureAwait(false) на Task object. Благодаря асинхронному дизайну, связанному с вводом-выводом, система может достичь очень высокой масштабируемости, поскольку поток, который является ценным / ограниченным ресурсом на процесс, не теряется в режиме ожидания.

True Async / Natural Async / IO Async

Запрос True Async может обслуживать миллионы запросов по сравнению с несколькими сотнями в синхронной системе, базовая технология в Windows называется IO completion ports. Ему не нужен программный поток для обработки запроса, он использует аппаратный параллелизм

Асинхронная привязка процессора

С другой стороны, для асинхронного сценария с привязкой к процессору используется случай WPF или аналогичных толстых клиентов, которые могут выполнять фоновую обработку в отдельном потоке, используя Task.Run, что освобождает поток пользовательского интерфейса, делая систему намного более отзывчивой здесь вы не сохраняете поток, поскольку рабочий поток вызывается с использованием Task.Run, все в одном и том же процессе, но система намного быстрее реагирует, что является важным достижением благодаря асинхронной обработке.

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

Для приложения нет автоматического способа выбора, будь то ваш код или код, который вы вызываете, который бы создал рабочий поток. Для Async с привязкой к вводу-выводу вам необходимо вызвать правильный API, который бы внутренне использовал конвейер Async, предоставляемый Windows (порт завершения ввода-вывода), чтобы сделать его чистым Async

Я не уверен, что полностью понимаю «естественно асинхронный», но в качестве базового понимания кажется, что методы платформы .NET, которые предлагают асинхронные методы, естественно являются асинхронными, и WebClient.DownloadFile / DownlodFileAsync является одним из них.

Большинство методов .Net, предлагающих асинхронную версию, были бы связанными с IO-вызовами, привязка к ЦП должна быть явно создана с использованием Task.Run, а затем ожидалась. Для асинхронной реализации, основанной на событиях, TaskCompletionSource является хорошим вариантом, который возвращает ожидаемое текущее Task, которое может быть SetResult, SetException для установки результата или ошибки в событии, таким образом асинхронно завершая Task. TaskCompletionSource также является реализацией без потоков

Относительно различных методов, которые вы опубликовали

Ваше понимание относительно Method1 верно, пользователь может решить, использовать ли фоновый поток для его вызова, так как он освобождает основной вызывающий поток, который может быть потоком пользовательского интерфейса. Что касается Method2, как уже упоминалось в комментариях, убедитесь, что это Async до самого верха (точка входа), поскольку это единственный способ освободить вызывающий поток, который будет использоваться в других операциях

Наконец

Этот код очень плохая идея, также упоминается в комментариях @ LexLi

Public static Task DownloadToCacheAsync()
{
    return Task.Run(()=>DownloadToCache());
}

Public static void DownloadToCache()
{
    DownloadToCacheAsync().Wait();
}

Пара баллов:

  1. Выполнение Task.Run в синхронном методе, связанном с вводом-выводом, не будет иметь никаких преимуществ, поскольку рабочий поток в любом случае блокируется, поэтому поток ожидает простоя в отличие от асинхронного метода, выигрыш в масштабируемости системы отсутствует, поскольку потоки блокируются
  2. Если метод имеет версию Async, то он обязательно будет иметь версию Sync, которую необходимо использовать просто DownloadToCache(). Синхронизация предшествует Async, и я не знаю случая только с асинхронным вызовом, но без синхронизации
  3. Явное Task.Wait() выполняется в вызывающем потоке, что делает систему не отвечающей на запросы и даже в некоторых случаях может привести к взаимоблокировке, поскольку основной поток заблокирован, в то время как другие операции должны выполняться в основном / вызывающем потоке
  4. Единственной причиной использования Task.Wait() является обработка некоторой логики в фоновом / рабочем / потоке пула потоков, которую нужно ждать и, возможно, использовать результат перед дальнейшей логической обработкой, главным образом для операций, связанных с ЦП. Это не для операций ввода-вывода

Надеюсь, это поможет прояснить вопрос об использовании Async-Await apis

...