Подтвердить что ты не робот

Асинхронное и параллельное выполнение MVC4

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

Возьмите эту модель:

public class TestOutput
{
    public string One { get; set; }
    public string Two { get; set; }
    public string Three { get; set; }

    public static string DoWork(string input)
    {
        Thread.Sleep(2000);
        return input;
    }
}

Используется в контроллере следующим образом:

public void IndexAsync()
{
    AsyncManager.OutstandingOperations.Increment(3);

    Task.Factory.StartNew(() => 
        { 
            return TestOutput.DoWork("1"); 
        })
        .ContinueWith(t => 
        { 
            AsyncManager.OutstandingOperations.Decrement(); 
            AsyncManager.Parameters["one"] = t.Result; 
        });
    Task.Factory.StartNew(() =>
        {
            return TestOutput.DoWork("2");
        })
        .ContinueWith(t =>
        {
            AsyncManager.OutstandingOperations.Decrement();
            AsyncManager.Parameters["two"] = t.Result;
        });
    Task.Factory.StartNew(() =>
        {
            return TestOutput.DoWork("3");
        })
        .ContinueWith(t =>
        {
            AsyncManager.OutstandingOperations.Decrement();
            AsyncManager.Parameters["three"] = t.Result;
        });
}

public ActionResult IndexCompleted(string one, string two, string three)
{
    return View(new TestOutput { One = one, Two = two, Three = three });
}

Этот контроллер отображает представление в 2 секунды, благодаря магии TPL.

Теперь я ожидал (скорее наивно), что приведенный выше код переводится следующим образом, используя новые функции "async" и "ожидание" С# 5:

public async Task<ActionResult> Index()
{
    return View(new TestOutput
    {
        One = await Task.Run(() =>TestOutput.DoWork("one")),
        Two = await Task.Run(() =>TestOutput.DoWork("two")),
        Three = await Task.Run(() =>TestOutput.DoWork("three"))
    });
}

Этот контроллер отображает представление в 6 секунд. Где-то в переводе код стал уже не параллельным. Я знаю, что асинхронные и параллельные - это две разные концепции, но почему-то я думал, что код будет работать одинаково. Может ли кто-нибудь указать, что здесь происходит и как это можно исправить?

4b9b3361

Ответ 1

Где-то в переводе код стал больше не параллельным.

Точно. await будет (асинхронно) ждать завершения одной операции.

Параллельные асинхронные операции могут быть выполнены путем запуска фактического Task, но не await их до следующего:

public async Task<ActionResult> Index() 
{
  // Start all three operations.
  var tasks = new[]
  {
    Task.Run(() =>TestOutput.DoWork("one")), 
    Task.Run(() =>TestOutput.DoWork("two")), 
    Task.Run(() =>TestOutput.DoWork("three"))
  };

  // Asynchronously wait for them all to complete.
  var results = await Task.WhenAll(tasks);

  // Retrieve the results.
  return View(new TestOutput
  {
    One = results[0],
    Two = results[1],
    Three = results[2]
  }); 
} 

P.S. Там также Task.WhenAny.

Ответ 2

Нет, вы сказали, что это уже другое дело. Параллельные и Async - это две разные вещи.

Версия Task работает через 2 секунды, потому что она запускает три операции одновременно (пока у вас есть 3+ процессора).

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

Таким образом, большая разница между версией TPL и асинхронной версией заключается в том, что версия TPL работает в любом порядке, потому что все задачи независимы друг от друга. В то время как асинхронная версия запускается в порядке написания кода. Итак, если вы хотите использовать параллель, используйте TPL, и если вы хотите использовать async, используйте async.

Точка async - это возможность писать синхронно выглядящий код, который не будет блокировать пользовательский интерфейс при длительном действии. Однако это обычно действие, которое весь процессор делает, ждет ответа. Async/await делает так, чтобы код, называемый методом async, не дождался возврата метода async, вот и все. Итак, если вы действительно хотели подражать своей первой модели, используя async/await (что я бы предложил НЕ), вы могли бы сделать что-то вроде этого:

MainMethod()
{
    RunTask1();
    RunTask2();
    RunTask3();
}

async RunTask1()
{
    var one = await Task.Factory.StartNew(()=>TestOutput.DoWork("one"));
    //do stuff with one
}

async RunTask2()
{
    var two= await Task.Factory.StartNew(()=>TestOutput.DoWork("two"));
    //do stuff with two
}

async RunTask3()
{
    var three= await Task.Factory.StartNew(()=>TestOutput.DoWork("three"));
    //do stuff with three
}

Путь к коду будет выглядеть примерно так (если задачи выполняются долго)

  • главный вызов RunTask1
  • RunTask1 ожидает и возвращает
  • главный вызов RunTask2
  • RunTask2 ожидает и возвращает
  • главный вызов RunTask3
  • RunTask3 ожидает и возвращает
  • main теперь выполнено
  • RunTask1/2/3 возвращает и продолжает делать что-то с одним/двумя/тремя
  • То же, что и 7, за исключением того, что уже завершено
  • То же, что и 7, за исключением двух уже завершенных

****. Об этом говорит большой отказ. Ожидание будет выполняться синхронно, если задача уже завершена к моменту ожидания ожидания. Это избавляет среду выполнения от необходимости выполнять ее vudu:), поскольку она не нужна. Это сделает поток кода выше некорректным, так как поток теперь синхронный ****

Эрик Lippert сообщение в блоге об этом объясняет вещи намного лучше, чем я делаю:) http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

Надеюсь, это поможет развеять некоторые ваши вопросы об async и TPL? Самое большое, что нужно убрать, это то, что асинхронность не параллельна.