2008-12-03 11 views
14

Hier ist das Szenario:Vermeidung eines Javascript Race-Zustand

Meine Benutzer sind ein Raster, im Grunde genommen, eine abgespeckte Version einer Tabelle dargestellt. In jeder Zeile des Rasters befinden sich Textfelder. Wenn sie einen Wert in einem Textfeld ändern, führe ich eine Validierung ihrer Eingabe durch, aktualisiere die Sammlung, die das Raster steuert, und zeichne die Zwischensummen auf der Seite neu. Dies wird alles durch das OnChange-Ereignis jedes Textfelds behandelt.

Wenn sie auf die Schaltfläche "Speichern" klicken, verwende ich das OnClick-Ereignis der Schaltfläche, um eine abschließende Überprüfung der Beträge durchzuführen und dann ihre gesamten Eingaben an einen Webdienst zu senden und zu speichern.

Zumindest passiert das, wenn sie durch das Formular auf den Submit-Button klicken.

Das Problem ist, wenn sie einen Wert eingeben, klicken Sie sofort auf die Schaltfläche Speichern, SaveForm() startet die Ausführung vor UserInputChanged() - eine Race-Bedingung. Mein Code nicht verwendet SetTimeout nicht, aber ich bin mit dem träge UserInputChanged Validierungscode zu simulieren:

<!-- snip --> 
<script> 
    var amount = null; 
    var currentControl = null; 

    function UserInputChanged(control) { 
     currentControl = control; 
     // use setTimeout to simulate slow validation code (production code does not use setTimeout) 
     setTimeout("ValidateAmount()", 100); 
    } 

    function SaveForm() { 
     // call web service to save value 
     document.getElementById("SavedAmount").innerHTML = amount; 
    } 

    function ValidateAmount() { 
     // various validationey functions here 
     amount = currentControl.value; // save value to collection 
     document.getElementById("Subtotal").innerHTML = amount; // update subtotals 

    } 
</script> 
<!-- snip --> 
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br /> 
Subtotal: <span id="Subtotal"></span> <br /> 
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br /> 
Saved amount: <span id="SavedAmount"></span> 
<!-- snip --> 

ich mir beschleunigen kann den Validierungscode nicht denken - es ist ziemlich leicht, aber es scheint, langsam Genug, dass der Code versucht, den Web-Service aufzurufen, bevor die Validierung abgeschlossen ist.

Auf meinem Computer ist ~ 95ms die magische Zahl zwischen, ob der Überprüfungscode ausgeführt wird, bevor der Sicherungscode beginnt. Je nach Computergeschwindigkeit des Benutzers kann dies höher oder niedriger sein.

Hat jemand irgendwelche Ideen, wie man mit dieser Bedingung umgeht? Ein Kollege schlug vor, einen Semaphor zu verwenden, während der Validierungscode läuft, und eine Speicherschleife im Speichercode, um zu warten, bis der Semaphor entsperrt wird - aber ich möchte vermeiden, irgendeine Art von Besetztschleife in meinem Code zu verwenden.

Antwort

18

Verwenden Sie den Semaphor (nennen wir es StillNeedsValidating). Wenn die SaveForm-Funktion den StillNeedsValidating-Semaphor aktiviert hat, muss er einen zweiten Semaphor aktivieren (den ich hier FormNeedsSaving aufrufen werde) und zurückkehren. Wenn die Validierungsfunktion beendet ist und die Semaphore von FormNeedsSaving aktiv sind, ruft sie die SaveForm-Funktion selbst auf.

In jankcode;

function UserInputChanged(control) { 
    StillNeedsValidating = true; 
    // do validation 
    StillNeedsValidating = false; 
    if (FormNeedsSaving) saveForm(); 
} 

function SaveForm() { 
    if (StillNeedsValidating) { FormNeedsSaving=true; return; } 
    // call web service to save value 
    FormNeedsSaving = false; 
} 
+0

Genau das, was ich brauche. Vielen Dank! –

4

denke ich das Timeout Ihr Problem verursacht ... wenn das Normalcode (keine asynchrone AJAX-Aufrufe, Timeouts usw.) sein wird, dann glaube ich nicht, dass SaveForm ausgeführt wird, bevor UserInputChanged abgeschlossen ist.

+0

Ich stimme zu. Vielleicht ist eine bessere Simulation von "langsamem Code", nur eine große "for" -Schleife zu haben, die nichts tut. Auf diese Weise geben Sie nicht die Kontrolle über die JavaScript-Engine frei und sollten sicherstellen, dass die Ereignisse in der richtigen Reihenfolge behandelt werden. –

+0

Es gibt keine setTimeouts im eigentlichen Code. Es gibt eine Reihe langweiliger getElementByIds, eine isNan, eine parseInt und eine Überprüfung, um sicherzustellen, dass die Summe aller Textfelder nicht einen vordefinierten Betrag überschreitet - aber nichts asynchron. –

7

Deaktivieren Sie die Schaltfläche Speichern während der Validierung. Setzen Sie es als das, was die Validierung zuerst tut, und aktivieren Sie es wieder, wenn es fertig ist.

z.B.

function UserInputChanged(control) { 
    // --> disable button here --< 
    currentControl = control; 
    // use setTimeout to simulate slow validation code (production code does not use setTimeout) 
    setTimeout("ValidateAmount()", 100); 
} 

und

function ValidateAmount() { 
    // various validationey functions here 
    amount = currentControl.value; // save value to collection 
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals 
    // --> enable button here if validation passes --< 
} 

Sie werden einstellen müssen, um, wenn Sie die setTimeout entfernen und die Validierung einer Funktion machen, aber es sei denn, Ihre Benutzer übermenschliche Reflexe haben, sollten Sie gut zu gehen.

0

Sie könnten eine wiederkehrende Funktion einrichten, die den Status des gesamten Grids überwacht und ein Ereignis auslöst, das angibt, ob das gesamte Grid gültig ist oder nicht.

Ihr 'Formular absenden' Button würde sich dann basierend auf diesem Status aktivieren oder deaktivieren.

Oh, ich sehe jetzt eine ähnliche Antwort - das funktioniert natürlich auch.

1

Ein Semaphor oder Mutex ist wahrscheinlich der beste Weg zu gehen, aber anstelle einer geschäftigen Schleife, verwenden Sie einfach setTimeout(), um einen Thread Schlaf zu simulieren. Wie folgt aus:

busy = false; 

function UserInputChanged(control) { 
    busy = true; 
    currentControl = control; 
    // use setTimeout to simulate slow validation code (production code does not use setTimeout) 
    setTimeout("ValidateAmount()", 100); 
} 

function SaveForm() { 
    if(busy) 
    { 
     setTimeout("SaveForm()", 10); 
     return; 
    } 

    // call web service to save value 
    document.getElementById("SavedAmount").innerHTML = amount; 
} 

function ValidateAmount() { 
    // various validationey functions here 
    amount = currentControl.value; // save value to collection 
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals 
    busy = false; 
} 
1

Sie haben noch eine Race Condition, können Bedingungen Rennen nicht in JavaScript geschehen, da Javascript Single-Threaded ist, so 2 Fäden nicht miteinander stören.

Das Beispiel, das Sie geben, ist kein sehr gutes Beispiel. Der setTimeout-Aufruf setzt die aufgerufene Funktion in eine Warteschlange in der JavaScript-Engine und führt sie später aus. Wenn Sie zu diesem Zeitpunkt auf die Schaltfläche Speichern klicken, wird die setTimeout-Funktion erst aufgerufen, nachdem der Speichervorgang abgeschlossen ist.

Was wahrscheinlich in Ihrem Javascript passiert, ist, dass das onClick-Ereignis von der Javascript-Engine aufgerufen wird, bevor das onChange-Ereignis aufgerufen wird.

Als Hinweis, beachten Sie, dass Javascript Singlethread ist, es sei denn, Sie verwenden einen Javascript-Debugger (Firebug, Microsoft Skript Debugger). Diese Programme fangen den Thread ab und pausieren ihn. Von diesem Zeitpunkt an können andere Threads (entweder über Ereignisse, setTimeout-Aufrufe oder XMLHttp-Handler) ausgeführt werden, was den Eindruck erweckt, dass JavaScript mehrere Threads gleichzeitig ausführen kann.

+8

Natürlich können Sie in Javascript auch Rennbedingungen haben, es ist sehr asynchron! Es gibt nur einen UIhread, aber jeder

Verwandte Themen