简体   繁体   English

ASP中的异步Web服务。 NET MVC

[英]Asynchronous web service in ASP. NET MVC

I am writing an ASP.NET MVC 5 application which among others uses web services to get/process some the data. 我正在编写一个ASP.NET MVC 5应用程序,其中包括使用Web服务来获取/处理一些数据。

The data flow of the app is following: MVC Action -> Service B -> ExtSvc which is async wrapper of a web service 该应用程序的数据流如下:MVC操作 - >服务B - > ExtSvc,它是Web服务的异步包装器

Here are some examples: 这里有些例子:

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

Class B is the one that uses ExtSvc in a synchronous way 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 
    }
}

Finally the MVC action (also synchronous ) 最后是MVC动作( 同步

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

The described flow throws an error 所描述的流程会引发错误

A first chance exception of type 'System.InvalidOperationException' occurred in System.Web.dll System.Web.dll中发生了'System.InvalidOperationException'类型的第一次机会异常

Additional information: An asynchronous operation cannot be started at this time. 附加信息:此时无法启动异步操作。 Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. 异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。 If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. 如果在执行页面时发生此异常,请确保将页面标记为<%@ Page Async =“true”%>。 This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. 此异常还可能表示尝试调用“异步void”方法,该方法通常在ASP.NET请求处理中不受支持。 Instead, the asynchronous method should return a Task, and the caller should await it. 相反,异步方法应该返回一个Task,调用者应该等待它。

on the following line of ExtSvc : this.Service.ProcessRequestAsync(request, taskCompletionSource); 在ExtSvc的以下行: this.Service.ProcessRequestAsync(request, taskCompletionSource); ProcessRequestAsync is a void method So it corresponds to: ProcessRequestAsync是一个void方法所以它对应于:

This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing 此异常还可能表示尝试调用“异步void”方法,该方法通常在ASP.NET请求处理中不受支持

I know that converting GetResponse MVC action to asynchronous (by using async/await) and also converting the B class that actually uses ExtSvc to be asynchronous resolves the issue. 我知道将GetResponse MVC操作转换为异步(通过使用async / await)并将实际使用ExtSvc的B类转换为异步解决了这个问题。

BUT my questions is: 但我的问题是:

If I can't change the signature of B class (because of an interface it implements) to return Task<Response> instead of Response it basically means that I can't use async/await on it so how this issue could be resolved? 如果我不能更改B类的签名(因为它实现了一个接口)来返回Task<Response>而不是Response它基本上意味着我不能使用async / await,那么如何解决这个问题呢?

ProcessRequestAsync is void but it's not async void . ProcessRequestAsyncvoid但它不是async void It looks like it's an EBAP API . 它看起来像是一个EBAP API EBAP components generally use AsyncOperationManager / AsyncOperation , which in turn do use SynchronizationContext to notify the underlying platform of the asynchronous operation (the last link is to my MSDN article on SynchronizationContext ). EBAP组件通常使用AsyncOperationManager / AsyncOperation ,而AsyncOperationManager / AsyncOperation使用SynchronizationContext来通知底层平台异步操作 (最后一个链接是我在SynchronizationContext上的MSDN文章)。

The exception you're seeing is because ASP.NET sees that notification (of the asynchronous operation starting) and says "whoa, there, fella. You're a synchronous handler! No async for you!" 您看到的异常是因为ASP.NET看到该通知(异步操作开始)并说“哇,那里,家伙。你是同步处理程序!没有异步你!”

Hands-down, the best approach is to make all methods asynchronous that should be asynchronous. 动手,最好的方法是使所有方法异步,应该是异步的。 This means B.Update should be B.UpdateAsync . 这意味着B.Update应该是B.UpdateAsync OK, so there's an interface IB.Update - just change the interface to IB.UpdateAsync too. 好的,所以有一个接口IB.Update - 只需将接口更改为IB.UpdateAsync Then you're async all the way, and the code is clean. 然后你一直是异步,代码很干净。

Otherwise, you'll have to consider hacks. 否则,你将不得不考虑黑客攻击。 You could use Task.Run as @neleus suggested - that's a way of avoiding the ASP.NET SynchronizationContext so it doesn't "see" the asynchronous operation starting - but note that "ambient context" such as HttpContext.Current and page culture is lost. 您可以使用Task.Run作为@neleus建议 - 这是避免ASP.NET SynchronizationContext的一种方式,因此它不会“看到”异步操作开始 - 但请注意“环境上下文”,如HttpContext.Current和页面文化是丢失。 Or, you could (temporarily) install a new SynchronizationContext() onto the request thread - which also avoids the ASP.NET SynchronizationContext while staying on the same thread - but some ASP.NET calls assume the presence of the ASP.NET SynchronizationContext and will fail. 或者,您可以(暂时)将new SynchronizationContext()安装到请求线程上 - 这也避免了ASP.NET SynchronizationContext同时保留在同一个线程上 - 但是一些ASP.NET调用假定存在ASP.NET SynchronizationContext并且将失败。

There's another hack you could try; 你可以试试另一个黑客; it might work but I've never done it. 它可能会奏效,但我从来没有这样做过。 Just make your handler return a Task<ActionResult> and use Task.FromResult to return the view: return Task.FromResult<ActionResult>(View(...)); 只需让你的处理程序返回一个Task<ActionResult>并使用Task.FromResult来返回视图: return Task.FromResult<ActionResult>(View(...)); This hack will tell ASP.NET that your handler is asynchronous (even though it's not). 这个hack会告诉ASP.NET你的处理程序是异步的(即使它不是)。

Of course, all of these hacks have the primary disadvantage that you're doing sync-over-async ( this.ExtSvc.UpdateRequest(arguments).Result ), which means you'll be using one extra unnecessary thread for the duration of each request (or two threads, if you use the Task.Run hack). 当然,所有这些黑客都有一个主要的缺点,就是你正在进行同步异步( this.ExtSvc.UpdateRequest(arguments).Result ),这意味着你将在每个this.ExtSvc.UpdateRequest(arguments).Result的持续时间内使用一个额外的不必要的线程。请求(或两个线程,如果您使用Task.Run hack)。 So you will be missing all the benefits of using asynchronous handlers in the first place - namely, scalability. 因此,您将首先忽略使用异步处理程序的所有好处 - 即可伸缩性。

I think the error occurs because your code 我认为错误发生是因为您的代码

this.Service.ProcessRequestAsync(request, taskCompletionSource);

actually calls SynchronizationContext's OperationStarted method that results in error as described here . 实际上调用SynchronizationContext's OperationStarted方法描述导致错误这里

As a possible solution you can call your action on ThreadPoolSynchronizationContext 作为一种可能的解决方案,您可以在ThreadPoolSynchronizationContext上调用您的操作

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

but it adds some overhead of utilizing a thread from the pool. 但它增加了利用池中线程的一些开销。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM