Чи потрібно ставити Task.Run методом, щоб зробити його асинхронним?


304

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

Приклад 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.


30
Чи не ваш перший фрагмент дає вам попередження?
svick

Відповіді:


586

Спочатку давайте розберемо деяку термінологію: "асинхронний" ( async) означає, що він може повернути керування поточному виклику до запуску. У asyncметоді ці точки "дохідності" є 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код залежав від "очікуваного" в його awaitвиразах. Ці "очікувані" можуть бути іншими asyncметодами або просто звичайними методами, що повертають очікувані. Звичайні методи , які повертають 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інтро в моєму блозі; наприкінці - кілька хороших подальших ресурсів. Документи MSDN для asyncтакож незвичайно хороші.


8
@sgnsajgon: Так. asyncметоди повинні повернутися Task, Task<T>або void. Taskі Task<T>чекають; voidне.
Стівен Клірі

3
Насправді, async voidпідпис методу буде зібраний, це просто дуже жахлива ідея, коли ви втрачаєте покажчик на своє завдання асинхронізації
IEatBagels

4
@TopinFrassi: Так, вони voidзбиратимуться , але це не дочекається.
Стівен Клірі

4
@ohadinho: Ні, про те, про що я говорю в публікації блогу, це те, коли весь метод є лише закликом Task.Run(як DoWorkAsyncу цій відповіді). Використання Task.Runдля виклику методу з контексту інтерфейсу є доречним (як DoVariousThingsFromTheUIThreadAsync).
Стівен Клірі

2
Так, саме. Це дійсно використовувати Task.Runдля виклику методу, але якщо Task.Runнавколо методу існує майже весь (або майже весь) код, то це анти-шаблон - просто тримайте цей метод синхронізованим і переміщуйте рівень Task.Runвгору.
Стівен Клірі

22

Одне з найважливіших, що слід пам’ятати при декоруванні методу асинхронією, - це те, що принаймні є один оператор, що очікує, всередині методу. У вашому прикладі я б переклав це, як показано нижче, використовуючи 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();
}

26
Чому ви використовуєте TaskCompletionSource замість того, щоб просто повернути завдання, повернене методом Task.Run () (і змінити його тіло для повернення результату)?
іронічно

4
Просто бічна записка. Метод, який має підпис "асинхронізована недійсність", як правило, є поганою практикою і вважається поганим кодом, оскільки він може досить легко призвести до тупикового інтерфейсу користувача. Основний виняток - асинхронні обробники подій.
Jazzeroki

12

Коли ви використовуєте Task.Run для запуску методу, Task отримує нитку з нитки пулу для запуску цього методу. Отже, з точки зору потоку користувальницького інтерфейсу, він "асинхронний", оскільки він не блокує потік інтерфейсу користувача. Це добре для настільних додатків, оскільки вам зазвичай не потрібно багато потоків, щоб подбати про взаємодію користувачів.

Однак для веб-додатків кожен запит обслуговується потоком пулу потоків, і, таким чином, кількість активних запитів може бути збільшена, зберігаючи такі потоки. Часто використання потоків ниток для імітації роботи асинхронізації не є масштабним для веб-додатків.

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

У вашому простому прикладі це чистий розрахунок, пов'язаний з процесором, тому використання Task.Run прекрасно.


Отже, якщо мені доводиться споживати синхронну зовнішню api в межах веб-контролера api, я НЕ повинен перетворювати синхронний виклик у Task.Run ()? Як ви вже сказали, це дозволить зберегти початковий потік запиту розблокованим, але він використовує інший потік пулу для виклику зовнішніх api. Насправді я думаю, що це все-таки гарна ідея, оскільки таким чином він може теоретично використовувати два потоки пулу для обробки багатьох запитів, наприклад, один потік може обробити багато вхідних запитів, а інший може викликати зовнішній api для всіх цих запитів?
stt106

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

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()це правильно. Якщо ви це зробите, ви б просто перемикали потоки. тобто ви розблокуєте початковий потік запиту, але ви берете інший потік з нитки потоків, який міг би бути використаний для обробки іншого запиту. Єдиний результат - це перемикання контексту накладних витрат, коли виклик завершений для абсолютно нульового посилення
Saeb Amini
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.