【问题标题】:MVC4 async and Parallel executionMVC4 异步和并行执行
【发布时间】:2012-02-22 15:53:08
【问题描述】:

所以我正试图了解 .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 秒 内呈现视图。在翻译的某个地方,代码变得不再平行。我知道异步和并行是两个不同的概念,但不知何故,我认为代码的工作方式相同。有人能指出这里发生了什么以及如何解决吗?

【问题讨论】:

  • 所以基本上我混合了异步性和并行性。我(错误地)认为最终使用任务的结果时会发生实际的等待(与仅在枚举时执行 linq 查询不同)。

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


【解决方案1】:

在翻译的某个地方,代码变得不再并行。

没错。 await 将(异步)等待单个操作完成。

可以通过启动实际的Tasks 来完成并行异步操作,但要等到以后才能awaiting它们:

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]
  }); 
} 

附:还有一个Task.WhenAny

【讨论】:

  • 好的,我了解异步和并行之间的区别(我在提问时自己猜到了),但是“等待”Task.WhenAll 的结果是否有必要?如果控制器中的操作已经是“异步”的,那么在异步方法中等待不会对系统造成额外的负担吗?
  • 是的,这是必要的,因为您需要在构建视图之前完成任务。使用异步等待比同步阻塞的负担更轻——它与旧的AsyncManager 方法大致相同。
  • 重要的一点:async 关键字除了允许在该方法中使用 await 关键字之外,没有任何作用。是 await 关键字 - 而不是 async 关键字 - 使事情变得异步。所以你真的应该在每个async 方法的某个地方都有一个await(如果你不这样做,编译器会警告你)。
  • 好的,我想我现在get async/await。谢谢你的帮助。 Anser 接受了 :)
  • @Steve:在所有任务完成之前不会分配给results。此代码示例中没有线程被阻塞。
【解决方案2】:

不,您已经说明了不同的原因。并行和异步是两个不同的东西。

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

await其实就是这个样子,代码会等待Task.Run的执行,然后再继续下一行代码。

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

异步的重点是能够编写看起来同步的代码,在长时间运行的操作发生时不会锁定 UI。但是,这通常是所有处理器正在执行的操作,即等待响应。 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. main 现在完成了
  8. RunTask1/2/3 返回并继续用一/二/三做某事
  9. 与 7 相同,但减去已完成的那一项
  10. 与 7 相同,但减去已完成的两个

****不过,对此有很大的免责声明。如果在等待被命中时任务已经完成,则等待将同步运行。这使运行时不必执行它的 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 的一些疑问?最重要的是异步不是并行的。

【讨论】:

  • 我喜欢你的回答,但我接受了另一个,因为它包含我的示例的可行版本。
猜你喜欢
  • 1970-01-01
  • 2018-05-11
  • 2018-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-07
  • 2019-05-27
  • 1970-01-01
相关资源
最近更新 更多