繁体   English   中英

避免javascript竞争条件

[英]Avoiding a javascript race condition

这是场景:

我的用户被呈现为网格,基本上是电子表格的精简版本。 网格中的每一行都有文本框。 当他们更改文本框中的值时,我正在对其输入执行验证,更新驱动网格的集合,并重新绘制页面上的小计。 这全部由每个文本框的OnChange事件处理。

当他们点击“保存”按钮时,我正在使用按钮的OnClick事件对金额执行一些最终验证,然后将他们的整个输入发送到Web服务,保存它。

至少,如果他们将表单选中到“提交”按钮,则会发生这种情况。

问题是,如果他们输入一个值,那么立即单击保存按钮,SaveForm()在UserInputChanged()完成之前开始执行 - 一个竞争条件。 我的代码不使用setTimeout,但我用它来模拟缓慢的UserInputChanged验证代码:

 <!-- 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 -->

我不认为我可以加快验证代码 - 它非常轻量级,但显然,代码试图在验证完成之前调用Web服务的速度足够慢。

在我的机器上,〜95ms是验证代码在保存代码开始之前执行之间的幻数。 根据用户的计算机速度,这可能更高或更低。

有没有人有任何想法如何处理这种情况? 同事建议在验证代码运行时使用信号量,并在保存代码中使用繁忙循环等待信号量解锁 - 但我想避免在我的代码中使用任何类型的繁忙循环。

使用信号量(让我们称之为StillNeedsValidating)。 如果SaveForm函数看到StillNeedsValidating信号量已启动,请让它激活自己的第二个信号量(我在这里称之为FormNeedsSaving)并返回。 验证函数完成后,如果FormNeedsSaving信号量已启动,它将自行调用SaveForm函数。

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

验证期间禁用保存按钮。 将其设置为禁用,作为验证的第一件事,并在完成后重新启用它。

例如

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

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

你必须在删除setTimeout时调整并使验证成为一个函数,但除非你的用户有超人的反应,否则你应该好好去。

我认为超时导致你的问题...如果那将是普通代码(没有异步AJAX调用,超时等),那么我不认为在UserInputChanged完成之前将执行SaveForm。

信号量或互斥量可能是最好的方法,但不是繁忙的循环,只需使用setTimeout()来模拟线程休眠。 像这样:

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

您可以设置一个循环函数来监视整个网格的状态,并引发一个事件,指示整个网格是否有效。

然后,您的“提交表单”按钮将根据该状态启用或禁用自身。

哦,我现在看到类似的反应 - 当然也有效。

使用异步数据源时,您肯定会遇到竞争条件,因为JavaScript进程线程继续执行可能依赖于尚未从远程数据源返回的数据的指令。 这就是我们有回调函数的原因。

在您的示例中,对验证代码的调用需要具有回调函数,该函数可在验证返回时执行某些操作。

但是,在制作具有复杂逻辑或尝试排除或增强现有系列回调的内容时,您可以疯狂。

这就是我创建proto-q库的原因: http//code.google.com/p/proto-q/

如果你做了很多这类工作,请查看它。

你没有竞争条件,竞争条件不能在javascript中发生,因为javascript是单线程的,所以2个线程不能互相干扰。

你给出的例子不是一个很好的例子。 setTimeout调用将把被调用的函数放入javascript引擎的队列中,稍后运行。 如果此时单击保存按钮,则在保存完成之后才会调用setTimeout函数。

你的javascript中可能发生的事情是onClick事件在调用onChange事件之前由javascript引擎调用。

作为提示,请记住javascript是单线程的,除非你使用javascript调试器(firebug,microsoft screipt调试器)。 这些程序拦截线程并暂停它。 从那时起,其他线程(通过事件,setTimeout调用或XMLHttp处理程序)就可以运行了,这使得javascript可以同时运行多个线程。

暂无
暂无

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

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