Вам нужно поставить Task.Run в метод, чтобы сделать его асинхронным?

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

Пример 1:

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

Пример 2:

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

Если я жду DoWork1Async(), будет ли код выполняться синхронно или асинхронно?

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

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

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

Ответ 1

Во-первых, позвольте прояснить некоторую терминологию: "асинхронный" (async) означает, что он может вернуть управление вызывающему потоку до его запуска. В методе async те точки "yield" являются выражениями await.

Это очень отличается от термина "асинхронный", поскольку (неверно), используемый в документации MSDN в течение многих лет, означает "выполняется в фоновом потоке".

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

Хватит о том, чего они не представляют; вот что они:

  • Ключевое слово async позволяет использовать асинхронный метод (т.е. позволяет выражать выражения await). async могут возвращать Task, Task<T> или (если необходимо) void.
  • Любой тип, который следует за определенным шаблоном, может быть оправданным. Наиболее распространенными типами являются Task и Task<T>.

Итак, если мы переформулируем ваш вопрос на вопрос "как я могу запустить операцию в фоновом потоке так, как это ожидаемо", ответ заключается в использовании Task.Run:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(Но этот шаблон плохой подход, см. ниже).

Но если ваш вопрос: "Как мне создать метод async, который может вернуть его вызывающему, а не блокировать", ответ заключается в объявлении метода async и использовании await для его "уступчивости", указывает:

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

Итак, основной шаблон вещей заключается в том, чтобы код async зависел от "awaitables" в выражениях await. Этими "ожидающими" могут быть другие методы async или просто регулярные методы, возвращающие awaitables. Регулярные методы, возвращающие Task/Task<T>, могут использовать Task.Run для выполнения кода в фоновом потоке или (чаще) они могут использовать TaskCompletionSource<T> или один из его ярлыков (TaskFactory.FromAsync, Task.FromResult и т.д.), Я не рекомендую обернуть весь метод в Task.Run; синхронные методы должны иметь синхронные сигнатуры, и его следует оставить до потребителя, следует ли его обернуть в Task.Run:

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

У меня есть async/await intro в моем блоге; в конце есть некоторые хорошие последующие ресурсы. Документы MSDN для async также необычайно хороши.

Ответ 2

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

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

Ответ 3

Когда вы используете Task.Run для запуска метода, Task получает поток от threadpool для запуска этого метода. Таким образом, с точки зрения пользовательского интерфейса, он является "асинхронным", поскольку он не блокирует поток пользовательского интерфейса. Это прекрасно подходит для настольных приложений, так как вам обычно не нужно много потоков для обслуживания пользовательских взаимодействий.

Однако для веб-приложения каждый запрос обслуживается потоком пула потоков, и, таким образом, количество активных запросов может быть увеличено за счет сохранения таких потоков. Часто использование потоков threadpool для имитации асинхронной работы не масштабируется для веб-приложений.

True Async не обязательно включает использование потоков для операций ввода-вывода, таких как доступ к файлам/БД и т.д. Вы можете прочитать это, чтобы понять, почему операции ввода-вывода не нужны потоки. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

В вашем простом примере это чистый вычисление, связанное с ЦП, поэтому использование Task.Run в порядке.