【问题标题】:Asynchronous web service in ASP. NET MVCASP 中的异步 Web 服务。网络MVC
【发布时间】:2015-07-13 17:08:45
【问题描述】:

我正在编写一个 ASP.NET MVC 5 应用程序,其中使用 Web 服务来获取/处理一些数据。

应用程序的数据流如下:MVC Action -> Service B -> ExtSvc,它是 Web 服务的异步包装器

这里有一些例子:

public class ExtSvc
{       
//Convert Event based async pattern to task based async pattern:

        private Task<Response> ProcessExtRequestAsync(Request request)
        {
            TaskCompletionSource<Response> taskCompletionSource =
                AsyncServiceClientHelpers.CreateSource<Response>(request);
            ProcessRequestCompletedEventHandler handler = null;
            handler =
                (sender, e) =>
                AsyncServiceClientHelpers.TransferCompletion(
                    taskCompletionSource, 
                    e, 
                    () => e.Result, 
                    () => this.Service.ProcessRequestCompleted -= handler);
            this.Service.ProcessRequestCompleted += handler;
            try
            {
              this.Service.ProcessRequestAsync(request, taskCompletionSource);
            }
            catch (Exception)
            {
                this.Service.ProcessRequestCompleted -= handler;
                taskCompletionSource.TrySetCanceled();
                throw;
            }

                return taskCompletionSource.Task;
            }

            //Usage:

            public async Task<Response> UpdateRequest(some arguments)
            {
                //Validate arguments and create a Request object

            var response = await this.ProcessExtRequestAsync(request)
            .ConfigureAwait(false);

            return response;
        }
}

B 类是以同步的方式使用 ExtSvc

public class B
{
    public ExtSvc service {get; set;}

    public Response Update(arguments)
    {
       //some logic
       var result = this.ExtSvc.UpdateRequest(arguments).Result;
       //some logic
       return result 
    }
}

最后是 MVC 动作(也是同步

public ActionResult GetResponse(int id)
{
     //some logic
     B.Update(id);
     //some logic
     return View(...);
}

描述的流程抛出错误

“System.InvalidOperationException”类型的第一次机会异常 发生在 System.Web.dll

附加信息:异步操作无法在 这次。异步操作只能在一个 异步处理程序或模块或在页面中的某些事件期间 生命周期。如果在执行页面时发生此异常,请确保 该页面被标记为 。这个例外可能 还表示尝试调用“async void”方法,即 在 ASP.NET 请求处理中通常不受支持。相反, 异步方法应该返回一个Task,调用者应该等待 它。

在 ExtSvc 的以下行:this.Service.ProcessRequestAsync(request, taskCompletionSource); ProcessRequestAsync 是一个 void 方法 所以它对应于:

这个例外可能 还表示尝试调用“async void”方法,即 在 ASP.NET 请求处理中通常不受支持

我知道将 GetResponse MVC 操作转换为异步(通过使用 async/await)并将实际使用 ExtSvc 的 B 类转换为异步可以解决问题。

但我的问题是:

如果我不能更改 B 类的签名(因为它实现了一个接口)以返回 Task&lt;Response&gt; 而不是 Response 这基本上意味着我不能在它上面使用 async/await 那么这个问题如何能解决吗?

【问题讨论】:

  • 您不能更改 B 类的签名(因为它实现了一个接口),但是您可以让它实现一个带有Task&lt;T&gt; 签名的新接口,并使用那个来代替吗?否则,您将陷入阻塞同步调用;在 ASP.NET 中使用 Task.Run 包装它们没有意义。
  • @Alex This SO post 可以为您提供更多细节。

标签: c# asp.net-mvc web-services asynchronous async-await


【解决方案1】:

ProcessRequestAsyncvoid 但不是 async void。看起来是EBAP API。 EBAP 组件generally use AsyncOperationManager/AsyncOperation,然后是do use SynchronizationContext to notify the underlying platform of the asynchronous operation(最后一个链接是我在SynchronizationContext 上的MSDN 文章)。

您看到的异常是因为 ASP.NET 看到该通知(异步操作开始)并说“哇,那里,伙计。你是一个 同步 处理程序!没有异步你!”

毫无疑问,最好的方法是让所有应该异步的方法都异步。这意味着B.Update 应该是B.UpdateAsync。好的,所以有一个接口IB.Update - 只需将接口也更改为IB.UpdateAsync。然后一路异步,代码干净。

否则,您将不得不考虑黑客攻击。您可以按照@neleus 的建议使用Task.Run - 这是一种避免ASP.NET SynchronizationContext 的方法,因此它不会“看到”异步操作开始 - 但请注意“环境上下文”,例如HttpContext.Current 和页面文化消失了。或者,您可以(临时)将new SynchronizationContext() 安装到请求线程上——这也避免了 ASP.NET SynchronizationContext,同时保持在同一个线程上——但是一些 ASP.NET 调用 assume的 ASP.NET SynchronizationContext 并且会失败。

您可以尝试另一种技巧;它可能有效,但我从未做过。只需让你的处理程序返回一个Task&lt;ActionResult&gt; 并使用Task.FromResult 返回视图:return Task.FromResult&lt;ActionResult&gt;(View(...)); 这个 hack 将告诉 ASP.NET 你的处理程序是异步的(即使它不是)。

当然,所有这些 hack 的主要缺点是您正在执行异步同步 (this.ExtSvc.UpdateRequest(arguments).Result),这意味着您将在每个请求期间使用一个额外的不必要线程(或 两个线程,如果你使用Task.Run hack)。因此,您将首先失去使用异步处理程序的所有好处 - 即可伸缩性。

【讨论】:

  • 非常感谢您的回答(以及您出色的博客)。我知道所有建议的方法,您的回答实际上证实我没有错过任何可靠的解决方案。
【解决方案2】:

我认为发生错误是因为您的代码

this.Service.ProcessRequestAsync(request, taskCompletionSource);

实际上调用了SynchronizationContext'sOperationStarted方法,导致如here所述的错误。

作为一种可能的解决方案,您可以在 ThreadPoolSynchronizationContext 上调用您的操作

public async Task<ActionResult> GetResponse(int id)
{
     //some logic
     await Task.Run(() => { B.Update(id); });
     //some logic
     return View(...);
}

但它增加了使用池中线程的一些开销。

【讨论】:

  • 在 ASP.NET 中使用 Task.Run 将导致上下文从线程池切换到另一个线程(所有可能来自该线程的线程池需要增长或不可能增长)和将当前线程释放到线程池并在任务完成时再次释放。
  • 是的,没错,但这是因为B.Update() 是一种同步方法
  • @neleus,谢谢你的回答。我知道这个解决方案,但正如 Paulo Morgado 所说,它增加了不必要的开销,实际上并没有增加应用程序响应能力的任何改进。如果没有其他解决方案,我认为最终会同步使用 Web 服务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-22
相关资源
最近更新 更多