Async MVC.NET action method blocks any other HTTP

2019-03-22 07:13发布

问题:

(I apologize for changing the question)

The following snippet is from a MVC.NET controller (.NET: v4.5; AspNet.MVC: v5.2.3) . After LongOperation is called, it:

  • Spawn a process
  • Waits for its completion
  • Monitors a few LOG files
  • Uses SignalR to notify browser of the progress from the LOG files

(I have omitted the code for simplicity)

All this works, only while LongOperation is running, no other HTTP requests are handled by the controllers.

They get handled after the LongOperation completes and the action method returns result to the AJAX call.

What am I messing up? Thank you in advance.

Update (for @angelsix comment): Here is a simplified setup:

  • I have removed async/await as advised
  • Added breakpoints as advised
  • Verified they are hit as explained in the above

Basically: same result, see the console.log-ed text and timestamps Will appreciate any help from the community. Thank you in advance!

GUI and log

Action methods in the Controller

[AjaxOnly]
public ActionResult _RunLongOperation(string hubId)
{
    try
    {
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(1000);
            ProgressNotifierHub.Notify(hubId, string.Format("Notification from _RunLongOperation {0}", i));
        }
        return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunLongOperation : OK");
    }
    catch (Exception)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "_RunLongOperation : NOK");
    }
}

[AjaxOnly]
public ActionResult _RunAnotherOperation(string hubId)
{
    return new HttpStatusCodeResult(HttpStatusCode.OK, "_RunAnotherOperation : OK");
}

Razor View (partial) and javascript with SignalR hub setup Ajax calls

<script src="~/signalr/hubs"></script> 

@{ 
    Layout = null;
}

<button id="longOperationBtn" type="button" class="t-button" style='width: 155px'>Long Operation</button>
<button id="anotherOperationBtn" type="button" class="t-button" style='width: 155px'>Another Operation</button>


<script type="text/javascript">

    $(function () {
        setupEventHandlers();
        setupProgressNorificator();
    });


    function setupEventHandlers() {

        $('#longOperationBtn').click(function (event) {
            requestOperation('_RunLongOperation')
        });

        $('#anotherOperationBtn').click(function (event) {
            requestOperation('_RunAnotherOperation')
        });
    }

    function requestOperation(method) {
        trace(method + ' requested');

        $.ajax({
            url: '/Profiles/Validate/' + method,
            type: 'GET',
            data: { hubId: $.connection.hub.id },
            contentType: 'application/json; charset=utf-8',
            success: function () {
                trace(method + ' completed');
            },
            error: function () {
                trace(method + ' failed');
            }
        });
    }

    function setupProgressNorificator(profileId) {
        var hub = $.connection.progressNotifierHub;
        hub.client.notify = function (notification) {
            console.log(notification);
        };
        $.connection.hub.start();
    }

    function trace(s) {
        console.log('[' + new Date().toUTCString() + '] ' + s);
    }

</script>

回答1:

It looks like you are running the client "test" on Chrome. I'm not sure what the 2018 limitation is (it seems to change from release to release), but browsers do limit the number of concurrent connections allowed to the same host. I believe Chrome's limit is 6 or 8.

The log you posted appears to be from the client side. If it is, the behavior you are seeing could actually be the client waiting for a free connection to connect to the server - the problem may not be with ASP.NET at all, but rather with how you are testing.

It would be easy to validate - write another test that you can run concurrently with your current test in a separate browser window that just calls the "short" operation. If it has latency, I'm wrong. If it doesn't, well hopefully I've helped out!



回答2:

The issue

Your response does not mention anything about caching. So I suspect the browser is caching the response and using that.

Verify

To verify press F12 in the browser to open developer tools and then see if the second response shows a status of 304 or states Cached

Solve

To prevent caching on the action, inside the action you want to do this

Prevent Caching in ASP.NET MVC for specific actions using an attribute



回答3:

According to this:

Some ... protocols like HTTP Polling (XHR) use up to two simultaneous connections per ... client. It is important to understand that the maximum number of connections is per browser and not per browser tab. Attempting to run multiple clients within the same browser might cause this limit to be reached.

In my case, SignalR client was using long polling. A fried reported that when Web sockets are used, there was no blocking issue. Closing the subject. Thanks for the help Joe.



回答4:

You need to set your controller to have read-only session state behavior. Then you can do ajax requests to the controller during running a long controller method. In other case (as the one that you are complaining about) the requests will be queued and call all at once after finishing the controller action call. Just put this before controller class definition

[SessionState(SessionStateBehavior.ReadOnly)]