MVC4 асинхронное и параллельное выполнение - PullRequest
13 голосов
/ 31 января 2012

Так что я пытаюсь разобраться с этим новым «асинхронным» материалом в .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» и «await» в C # 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 секунд . Где-то в переводе код больше не стал параллельным. Я знаю, что асинхронность и параллель - это две разные концепции, но почему-то я думал, что код будет работать одинаково. Может ли кто-нибудь указать, что здесь происходит и как это можно исправить?

Ответы [ 2 ]

18 голосов
/ 31 января 2012

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

Точно. 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.

7 голосов
/ 31 января 2012

Нет, вы указали причину, по которой это уже отличается. Parallel и Async - это разные вещи.

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

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

Итак, большая разница между версией TPL и асинхронной версией заключается в том, что версия TPL выполняется в любом порядке, поскольку все задачи не зависят друг от друга. Принимая во внимание, что асинхронная версия выполняется в порядке написания кода. Так что, если вы хотите параллельный, используйте TPL, а если вы хотите асинхронный, используйте 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
}

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

  1. основной вызов RunTask1
  2. RunTask1 ждет и возвращает
  3. основной вызов RunTask2
  4. RunTask2 ждет и возвращает
  5. основной вызов RunTask3
  6. RunTask3 ждет и возвращает
  7. Основное теперь сделано
  8. RunTask1 / 2/3 возвращается и продолжает что-то делать с одним / двумя / тремя
  9. То же, что 7, за исключением того, что уже завершено
  10. То же, что 7, за исключением двух уже законченных

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

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

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

...