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