Warum würden mehrere gleichzeitige AJAX-Aufrufe derselben ASP.NET MVC-Aktion dazu führen, dass der Browser blockiert wird?

74

Vor ein paar Tagen habe ich folgende Frage gestellt:

Warum blockiert $ .getJSON () den Browser?

Ich feuere sechs asynchrone jQuery-Ajax-Anforderungen gleichzeitig mit derselben Controller-Aktion ab. Jede Anfrage benötigt 10 Sekunden, um zurückzukehren.

Durch das Debuggen und Protokollieren von Anforderungen an die Aktionsmethode stelle ich fest, dass die Anforderungen serialisiert sind und niemals parallel ausgeführt werden. dh ich sehe eine Zeitleiste in meinen log4net-Protokollen wie folgt:

2010-12-13 13: 25: 06,633 [11164] INFO - Got: 1156
2010-12-13 13: 25: 16,634 [11164] INFO - Rückgabe: 1156
2010-12-13 13: 25: 16,770 [7124] INFO - Got: 1426
2010-12-13 13: 25: 26,772 [7124] INFO - Rückgabe: 1426
2010-12-13 13: 25: 26,925 [11164] INFO - Got: 1912
2010-12-13 13: 25: 36,926 [11164] INFO - Rückgabe: 1912
2010-12-13 13: 25: 37,096 [9812] INFO - Got: 1913
2010-12-13 13: 25: 47,098 [9812] INFO - Rückgabe: 1913
2010-12-13 13: 25: 47,283 [7124] INFO - Got: 2002
2010-12-13 13: 25: 57,285 [7124] INFO - Rückgabe: 2002
2010-12-13 13: 25: 57,424 [11164] INFO - Got: 1308
2010-12-13 13: 26: 07,425 [11164] INFO - Rückgabe: 1308

Wenn ich mir die Netzwerkzeitleiste in FireFox ansehe, sehe ich Folgendes:

Alt-Text

Sowohl das obige Protokollbeispiel als auch die Firefox-Netzwerkzeitleiste gelten für denselben Anforderungssatz.

Werden Anforderungen an dieselbe Aktion von derselben Seite serialisiert? Mir ist der serialisierte Zugriff auf das SessionObjekt in derselben Sitzung bekannt, aber es werden keine Sitzungsdaten berührt.

Ich habe den clientseitigen Code auf eine einzelne Anfrage (die am längsten laufende) reduziert, aber dies blockiert den Browser immer noch, dh nur wenn die Ajax-Anfrage abgeschlossen ist, reagiert der Browser auf einen Link-Klick.

Was ich hier (in den Entwicklertools von Chrome) auch beobachte, ist, dass beim Klicken auf einen Link, wenn eine lange laufende Ajax-Anfrage ausgeführt wird, Failed to load resourcesofort ein Fehler gemeldet wird, der darauf hindeutet, dass der Browser den Ajax getötet hat (oder versucht, ihn zu töten und zu warten?) Anfrage:

Alt-Text

Es dauert jedoch noch einige Zeit, bis der Browser zur neuen Seite umleitet.

Sind Ajax-Anfragen wirklich asynchron oder ist dies ein Kinderspiel, weil Javascript tatsächlich Single-Threaded ist?

Dauern meine Anfragen einfach zu lange, damit dies funktioniert?

Das Problem tritt auch in Firefox und IE auf.

Ich habe auch das Skript so geändert, dass es $.ajaxdirekt und explizit festgelegt wird async: true.

Ich führe dies unter IIS7.5 aus. Sowohl die Windows 2008R2- als auch die Windows 7-Version machen dasselbe.

Debug- und Release-Builds verhalten sich ebenfalls gleich.

Kev
quelle
Haben Sie dies in Firefox / IE getestet? Vielleicht ist es ein Chromproblem?
Aseem Gautam
@aseem - es ist das gleiche in F / Fox und IE
Kev
Möglicherweise ändert Ihr Skript die asynchrone Einstellung an einer anderen Stelle.
ZippyV
Können Sie FF / Firebug einchecken, wenn mehrere Anforderungen gesendet werden (Net Panel in Firebug)? Diese Zeitprotokolle scheinen nicht richtig zu sein. JQuery Ajax-Anforderungen sind mit Sicherheit asynchron und müssen parallel ausgeführt werden.
Aseem Gautam
1
@aseem - Die Anforderungsheader enthalten keine "gesendeten" Zeiten. Was ich damit sagen will ist, dass Firefox (oder Google) nicht nach einer vernünftigen Reihenfolge sortieren kann, weil alle auf einmal angefordert werden. Die Reihenfolge, die dort vorhanden ist, ist auf die Zeit zurückzuführen, die jeder zugrunde liegende XHR benötigt, um zu antworten und zu bestätigen, dass "Ja, tatsächlich habe ich diese Anfrage gesendet". Daher kann es aufgrund von Unterschieden bei der Erstellung eines TCP / IP zu einigen Millisekunden Unterschied zwischen den einzelnen Anfragen kommen Verbindung. Daher die Timeline außerhalb der Reihenfolge. Ich weiß jedoch, dass diese Anrufe serialisiert sind (die Protokollierung in der Controller-Aktion sagt mir dies) ..... Fortsetzung ...
Kev

Antworten:

90

Die Antwort starrte mich ins Gesicht.

Übersicht über den ASP.NET-Sitzungsstatus :

Der Zugriff auf den ASP.NET-Sitzungsstatus ist pro Sitzung exklusiv. Wenn also zwei verschiedene Benutzer gleichzeitig Anforderungen stellen, wird der Zugriff auf jede einzelne Sitzung gleichzeitig gewährt. Allerdings , wenn zwei gleichzeitige Anforderungen für die gleiche Sitzung gemacht werden (durch den gleichen SessionID - Wert verwendet wird ), die erste Anforderung erhält exklusiven Zugriff auf die Sitzungsinformationen. Die zweite Anforderung wird erst ausgeführt, nachdem die erste Anforderung abgeschlossen wurde.

Ärgerlicherweise hatte ich diesen Absatz vor ein paar Wochen überflogen, ohne die volle Wirkung der kühnen Sätze wirklich zu berücksichtigen. Ich hatte gelesen, dass einfach "Zugriff auf den Sitzungsstatus serialisiert ist" und nicht "alle Anforderungen, unabhängig davon, ob Sie den Sitzungsstatus berühren oder nicht, serialisiert werden", wenn die Anforderungen aus derselben Sitzung stammen.

Glücklicherweise gibt es in ASP.NET MVC3 eine Problemumgehung und es ist möglich, sitzungslose Controller zu erstellen. Scott Guthrie spricht hier darüber:

Ankündigung von ASP.NET MVC 3 (Release Candidate 2)

Ich habe MVC3 RC2 installiert und das Projekt aktualisiert. Das Dekorieren des betreffenden Controllers mit [SessionState(SessionStateBehavior.Disabled)]löst das Problem.

Und natürlich habe ich das normalerweise erst vor ein paar Minuten in Stack Overflow gefunden:

Asynchronous Controller blockiert Anforderungen in ASP.NET MVC über jQuery

Kev
quelle
Danke dafür. Ich hatte genau das gleiche Problem.
Nick Olsen
Vielen Dank für die Hilfe bei einem harten Fehler! Ist es nur ich oder ist dieses auffallend dumme Standardverhalten?
rufen
6
Beachten Sie, dass die Verwendung [SessionState(SessionStateBehavior.Disabled)]möglicherweise eine Sicherheitsverletzung in Ihrer Anwendung auslöst, z. B. den Zugriff auf private Daten bei mehreren gleichzeitigen Anforderungen. Mit dieser Option können Sie nicht feststellen, ob ein Benutzer protokolliert ist oder ob eine Sitzung aktiv ist usw. Wenn Sie Sie müssen auf die Sitzung zugreifen, die Sie verwenden können [SessionState(SessionStateBehavior.ReadOnly)], damit Sie weiterhin Zugriff auf Ihre Sitzung haben, obwohl Sie sie nicht bearbeiten können, aber in vielen Fällen dennoch hilfreich sein können.
Gregoire D.
1
@ GregoireD. - Danke für die Infos über SessionStateBehavior.ReadOnly. Ich habe das Gefühl, dass ich mir das angeschaut habe und dass es immer noch einen serialisierten Zugriff auf Ihre verursacht Session. Die App verwendet die Formularauthentifizierung sowie eine Cookie / DB-Suchkombination, um den Zugriff auf diesen Controller zu sperren. Die vom Controller selbst zurückgegebenen Daten sind nicht vertraulich, aber wir haben Maßnahmen ergriffen, um zu verhindern, dass Menschen aus müßiger Neugier herumspielen.
Kev
1
SessionStateBehaviour.ReadOnly scheint in meinem Fall zu funktionieren und ermöglicht weiterhin den Zugriff auf die Sitzung.
Miika L.
9

Ich habe versucht, dies zu reproduzieren, konnte es aber nicht. Hier ist mein Test:

private static readonly Random _random = new Random();

public ActionResult Ajax()
{
    var startTime = DateTime.Now;
    Thread.Sleep(_random.Next(5000, 10000));
    return Json(new { 
        startTime = startTime.ToString("HH:mm:ss fff"),
        endTime = DateTime.Now.ToString("HH:mm:ss fff") 
    }, JsonRequestBehavior.AllowGet);
}

Und der Anruf:

<script type="text/javascript" src="/scripts/jquery-1.4.1.js"></script>
<script type="text/javascript">
    $(function () {
        for (var i = 0; i < 6; i++) {
            $.getJSON('/home/ajax', function (result) {
                $('#result').append($('<div/>').html(
                    result.startTime + ' | ' + result.endTime
                ));
            });
        }
    });
</script>

<div id="result"></div>

Und die Ergebnisse:

13:37:00 603 | 13:37:05 969
13:37:00 603 | 13:37:06 640
13:37:00 571 | 13:37:07 591
13:37:00 603 | 13:37:08 730
13:37:00 603 | 13:37:10 025
13:37:00 603 | 13:37:10 166

Und die FireBug-Konsole:

Alt-Text

Wie Sie sehen, wird die AJAX-Aktion parallel ausgeführt.


AKTUALISIEREN:

Es scheint, dass in meinen ersten Tests die Anforderungen tatsächlich in FireFox 3.6.12 und Chrome 8.0.552.215 in die Warteschlange gestellt werden, wenn sie verwendet werden $.getJSON(). Es funktioniert gut in IE8. Meine Tests wurden mit einem ASP.NET MVC 2-Projekt, VS2010, Cassini-Webserver, Windows 7 x64-Bit durchgeführt.

Nun , wenn ich ersetzen $.getJSON()mit $.get()ihm gut unter allen Browsern funktioniert. Das lässt mich glauben, dass es etwas gibt, $.getJSON()das dazu führen kann, dass die Anforderungen in die Warteschlange gestellt werden. Vielleicht könnte jemand, der mit den Interna des jQuery-Frameworks besser vertraut ist, mehr Licht in diese Angelegenheit bringen.


UPDATE 2:

Versuchen Sie die Einstellung cache: false:

$.ajax({
    url: '/home/ajax', 
    cache: false,
    success: function (result) {
        $('#result').append($('<div/>').html(
            result.startTime + ' | ' + result.endTime
        ));
    }
});
Darin Dimitrov
quelle
@ Kevin, ja, es ist ein bisschen seltsam. Könnten Sie versuchen, $.getJSON()durch zu ersetzen $.get()? Ich habe das mit Cassini getestet. Ich habe IIS nicht installiert und kann es nicht überprüfen.
Darin Dimitrov
@darin - Ich habe es versucht $.getJSON()und bin direkt zu gegangen $.ajax()und die Ergebnisse sind die gleichen.
Kev
Das Ausführen dieses Beispiels in IE8 funktioniert für mich einwandfrei. In FF und Chrome werden die Anforderungen jedoch nicht parallel gesendet. Ich benutze Cassini und VS2010.
Uvita
@uvita, macht das Ersetzen $.getJSON()durch $.get()einen Unterschied? Für mich macht es einen Unterschied. Bei Verwendung $.getJSONscheinen die Anforderungen in der Warteschlange zu stehen.
Darin Dimitrov
@ Darin, nein, hat es nicht. Genau die gleichen Ergebnisse in jedem Browser.
Uvita