MVC4异步和并行执行

Lod*_*ijk 13 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这样的控制器中使用哪个:

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 });
}
Run Code Online (Sandbox Code Playgroud)

由于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"))
    });
}
Run Code Online (Sandbox Code Playgroud)

此控制器在6秒内呈现视图.在翻译的某处,代码变得不再平行.我知道异步和并行是两个不同的概念,但不知怎的,我认为代码的工作原理是一样的.有人能指出这里发生了什么以及如何解决这个问题?

Ste*_*ary 18

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

正是.await将(异步)等待单个操作完成.

并行异步操作可以通过启动实际的Tasks 来完成,但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]
  }); 
} 
Run Code Online (Sandbox Code Playgroud)

PS还有一个Task.WhenAny.


Jus*_*ony 7

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

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

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

因此,TPL版本和异步版本之间的最大区别在于TPL版本以任何顺序运行,因为所有任务都是相互独立的.然而,异步版本按照编写代码的顺序运行.因此,如果您想要并行,请使用TPL,如果您想要异步,请使用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
}
Run Code Online (Sandbox Code Playgroud)

代码路径将是这样的(如果任务长时间运行)

  1. 主要调用RunTask1
  2. RunTask1等待并返回
  3. 主要调用RunTask2
  4. RunTask2等待并返回
  5. 主要调用RunTask3
  6. RunTask3等待并返回
  7. 主要是现在完成
  8. RunTask1/2/3返回并继续执行一个/两个/三个操作
  9. 与7相同,除了已经完成的那个
  10. 与7相同,除了已经完成的两个

****虽然这是一个很大的免责声明.如果任务在await被命中时已经完成,则Await将同步运行.这节省了运行时不必执行其vudu :),因为它不需要.这将使上面的代码流不正确,因为流现在是同步的****

Eric Lippert关于此的博文解释了比我正在做的更好的事情:) http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-部分两何处,await.aspx

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