MVC4异步和并行执行

时间:2012-01-30 21:18:01

标签: asp.net-mvc asynchronous task-parallel-library

所以我试图了解.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 });
}

由于TPL的神奇之处,此控制器以 2秒呈现视图。

现在我期望(相当天真地)使用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 个答案:

答案 0 :(得分:18)

  

在翻译的某处,代码变得不再平行。

正。 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

答案 1 :(得分:7)

不,你说的原因已经不同了。并行和异步是两回事。

任务版本在2秒内工作,因为它同时运行三个操作(只要你有3个以上的处理器)。

await实际上就是这样,代码将等待Task.Run的执行,然后再继续下一行代码。

因此,TPL版本和异步版本之间的最大区别在于TPL版本以任何顺序运行,因为所有任务都是相互独立的。然而,异步版本按照编写代码的顺序运行。因此,如果您想要并行,请使用TPL,如果您想要异步,请使用async。

async的重点是能够编写同步外观代码,在长时间运行的操作发生时不会锁定UI。但是,这通常是所有处理器正在执行的操作正在等待响应。 async / await使得调用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. main现已完成
  8. RunTask1 / 2/3返回并继续使用一个/两个/三个
  9. 执行某些操作
  10. 与7相同,但已完成的
  11. 除外
  12. 与7相同,除了已完成的两个
  13. **** 这是一个很大的免责声明。如果任务在await被命中时已经完成,则Await将同步运行。这节省了运行时不必执行其vudu :),因为它不需要。这将使上面的代码流不正确,因为流现在是同步的 * ** *

    Eric Lippert关于此的博文解释了比我做得更好的事情:) http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

    希望这有助于消除您关于异步与TPL的一些问题?最重要的是异步不是平行的。