【问题标题】:How to fire an asynchronous task, but wait for all callbacks before returning ActionResult?如何触发异步任务,但在返回 ActionResult 之前等待所有回调?
【发布时间】:2011-09-05 21:36:01
【问题描述】:

我有一个 ASP.NET MVC 3 操作方法,它在 HTTP POST 中接受 HttpFileCollectionBase

在这种方法中,我需要调整图像大小并上传 3 次。

action 方法目前如下所示:

public ActionResult ChangeProfilePicture()
{
   var fileUpload = Request.Files[0];

   ResizeAndUpload(fileUpload.InputStream, Size.Original);
   ResizeAndUpload(fileUpload.InputStream, Size.Profile);
   ResizeAndUpload(fileUpload.InputStream, Size.Thumb);

   return Content("Success", "text/plain");   
}

基本上这是一个用户个人资料页面,他们会在其中更改个人资料图片。上传通过 jQuery AJAX 进行。

现在,我怎样才能将三个 ResizeAndUpload 调用作为异步任务触发,但在所有三个任务都完成之前不返回操作结果?

之前我一直使用Task.Factory.StartNew 来触发异步任务,但那时我并不关心等待结果。

有什么想法吗?

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-3 asynchronous asynccallback


    【解决方案1】:

    一种简单的方法是使用 Join:

    public ActionResult ChangeProfilePicture()
    {
       var fileUpload = Request.Files[0];
       var threads = new Thread[3];
       threads[0] = new Thread(()=>ResizeAndUpload(fileUpload.InputStream, Size.Original));
       threads[1] = new Thread(()=>ResizeAndUpload(fileUpload.InputStream, Size.Profile));
       threads[2] = new Thread(()=>ResizeAndUpload(fileUpload.InputStream, Size.Thumb));
    
       threads[0].Start();
       threads[1].Start();
       threads[2].Start();
    
       threads[0].Join();
       threads[1].Join();
       threads[2].Join();
    
       return Content("Success", "text/plain");   
    }
    

    尽管 ResizeAndUpload 方法可能在某处阻塞(如果没有看到代码就无法确定),但在这种情况下可能值得重构它们以使其异步。

    【讨论】:

    • 调整大小和上传做两件事:1) 使用 GDI 调整图片大小,2) 上传图片到亚马逊。 GDI 的东西很快,亚马逊上传需要一段时间(2 mb 文件大约需要 10 秒)。所以这就是我想要异步执行它们的原因。所以没有阻塞,只是S3服务器上传延迟。
    • 上传到亚马逊的代码,是异步的吗?如果不是,那么这三个线程将只是坐在那里并在等待亚马逊的响应时被阻塞。这是不好的,特别是如果这个 Action 可以被多个请求同时调用。看看您是否可以将这些调用转换为异步调用。这可能会使事情复杂一点,但非常值得......
    • 是的,亚马逊上传是同步的。但我想等待它,因为我想在上传后使用客户端脚本刷新用户的个人资料图片。当然,另一种方法是在不关心回调的情况下使上传异步,并且只显示一条消息“您的上传已排队等待处理”等
    • @RPM1984 @BFree 询问亚马逊上传是否是a同步的 :) 只要它是同步的(即阻塞),那么这个 Thread.Join() 样式应该完全正确你需要什么。
    • @Jedidja - 是的,我知道他问它是否是异步的,我应该说“不,它是同步的”。我不应该说“是的”。 :) 无论如何,这很好用,我将异步任务更改为仅使上传异步,因此它会立即返回客户端并及时刷新照片。谢谢大家。
    【解决方案2】:

    还可以使用Task.Factory.StartNew,类似于@BFree 的回答:

    public ActionResult ChangeProfilePicture()
    {
       var fileUpload = Request.Files[0];
       var threads = new Task[3];
       threads[0] = Task.Factory.StartNew(()=>ResizeAndUpload(fileUpload.InputStream, Size.Original));
       threads[1] = Task.Factory.StartNew(()=>ResizeAndUpload(fileUpload.InputStream, Size.Profile));
       threads[2] = Task.Factory.StartNew(()=>ResizeAndUpload(fileUpload.InputStream, Size.Thumb));
    
       Task.WaitAll(threads, 120000); // wait for 2mins.
    
       return Content("Success", "text/plain");   
    }
    

    现在确定是Thread 还是Task 更好。

    【讨论】:

    • 我认为@Bfree 使用 Join() 的答案可能更好,因为它不依赖于任意等待时间(例如 2 分钟)
    • @Jedidja - 这是一个可选参数,我可以省略。
    【解决方案3】:

    使用 Task 和 ManualResetEvent 的不同实现

    public ActionResult Sample()
        {
            var wh1 = new ManualResetEvent(false);
            var wh2 = new ManualResetEvent(false);
            var wh3 = new ManualResetEvent(false);
    
            Task.Factory.StartNew(new Action<object>(wh =>
            {
                // DoSomething();
                var handle = (ManualResetEvent)wh;
                handle.Set();
            }), wh1);
    
            Task.Factory.StartNew(new Action<object>(wh =>
            {
                // DoSomething();
                var handle = (ManualResetEvent)wh;
                handle.Set();
            }), wh2);
    
            Task.Factory.StartNew(new Action<object>(wh =>
            {
                // DoSomething();
                var handle = (ManualResetEvent)wh;
                handle.Set();
            }), wh3);
    
            WaitHandle.WaitAll(new[] { wh1, wh2, wh3 });
    
            return View();
        }
    

    希望这会有所帮助。

    【讨论】:

    • 我可能遗漏了一些东西,但这与我在下面的答案基本相同,但有更多样板代码?
    • 是的,但不同之处在于您可以控制何时解除阻塞
    【解决方案4】:

    这是我的看法,使用方便的“Task.WaitAll”静态方法等待..

    public MyViewModel LoadData()
    {
        MyViewModel viewModel = null;
    
        try
        {
            Task.Factory.StartNew(() =>
               {
                   var task1 = Task<MyViewModel>.Factory.StartNew(() =>
                   {
                       return BuildMyViewModel(args);
                   });
    
                   var task2 = Task<ViewModel2>.Factory.StartNew(() =>
                   {
                       return BuildViewModel2(args);
                   });
    
                   var task3 = Task<ViewModel3>.Factory.StartNew(() =>
                   {
                       return BuildViewModel3(args);
                   });
    
                   Task.WaitAll(task1, task2, task3);
    
                   viewModel = task1.Result;
    
                   viewModel.ViewModel2 = task2.Result;
                   viewModel.ViewModel3 = task3.Result;
    
               }).Wait();
        }
        catch (AggregateException ex)
        {
            System.Diagnostics.Trace.WriteLine(ex.StackTrace); 
            // ...
        }
    
        return viewModel;
    }
    

    【讨论】:

      【解决方案5】:

      还有一个用于 MVC 操作的异步模式,请参阅此链接:

      http://msdn.microsoft.com/en-us/library/ee728598.aspx

      您仍然可以使用任务,但不需要任何特殊处理以允许操作异步执行

      【讨论】:

      • 但问题是,我不希望 client-&gt;myserver 调用是异步的,我希望 myserver-&gt;amazon 调用是异步的。
      • 明白了。另一种选择是使用 3 个 Task 实例和 3 个 WaitHandles,例如 ManualResetEvent。我将使用此代码示例发布另一个答案
      【解决方案6】:

      您可以用子任务组成一个任务。当所有子任务都完成后,父任务将完成。

      例子:

      public ActionResult ChangeProfilePicture()
      {
          var fileUpload = Request.Files[0];
      
          Task.Factory.StartNew(() =>
          {
              Task.Factory.StartNew(() => 
                  ResizeAndUpload(fileUpload.InputStream, Size.Original), 
                  TaskCreationOptions.AttachedToParent);
      
              Task.Factory.StartNew(() => 
                  ResizeAndUpload(fileUpload.InputStream, Size.Profile), 
                  TaskCreationOptions.AttachedToParent);
      
              Task.Factory.StartNew(() => 
                  ResizeAndUpload(fileUpload.InputStream, Size.Thumb), 
                  TaskCreationOptions.AttachedToParent);
          }).Wait();
      
          return Content("Success", "text/plain");
      }
      

      【讨论】:

        猜你喜欢
        • 2014-11-26
        • 2014-10-01
        • 2011-12-21
        • 1970-01-01
        • 1970-01-01
        • 2019-06-11
        • 2023-03-24
        • 2017-07-12
        相关资源
        最近更新 更多