【问题标题】:Avoiding a Javascript race condition避免 Javascript 竞争条件
【发布时间】:2010-09-25 04:26:09
【问题描述】:

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

当他们点击 Save 按钮时,我使用按钮的OnClick 事件对金额进行最终验证,然后将他们的全部输入发送到 Web 服务,并保存它。

至少,如果他们在表单中切换到 Submit 按钮,就会发生这种情况。

问题是,如果他们输入一个值,然后立即单击保存按钮,SaveForm()UserInputChanged() 完成之前开始执行——这是一个竞争条件。我的代码没有使用setTimeout,但是我用它来模拟呆滞的UserInputChanged验证码:

 <script>
  var amount = null;
  var currentControl = null;

  function UserInputChanged(control) {
      currentControl = control;
      // use setTimeout to simulate slow validation code
      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;
  }
</script>

Amount:   <input type="text" onchange="UserInputChanged(this)">
Subtotal: <span id="Subtotal"></span>
<button onclick="SaveForm()">Save</button>
Saved amount: <span id="SavedAmount"></span>

我认为我无法加快验证代码的速度——它非常轻量级,但显然速度很慢,以至于代码会在验证完成之前尝试调用 Web 服务。

在我的机器上,~95ms 是在保存代码开始之前验证代码是否执行之间的幻数。这可能会更高或更低,具体取决于用户的计算机速度。

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

【问题讨论】:

    标签: javascript dom-events race-condition


    【解决方案1】:

    使用信号量(我们称之为 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;
    }
    

    【讨论】:

      【解决方案2】:

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

      例如

      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 并使验证成为一项功能时,您必须进行调整,但除非您的用户具有超人的反应能力,否则您应该很高兴。

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 我同意。也许对“慢代码”的更好模拟是只拥有一个什么都不做的大“for”循环。这样您就不会释放对 Javascript 引擎的控制,并应确保以正确的顺序处理事件。
        • 实际代码中没有setTimeouts。有一系列无聊的 getElementByIds、一个 isNan、一个 parseInt 和一个检查以确保所有文本框的总和不超过预定义的数量 - 但没有异步。
        【解决方案4】:

        信号量或互斥量可能是最好的方法,但不是繁忙的循环,而是使用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;
        }
        

        【讨论】:

          【解决方案5】:

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

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

          哦,我现在看到了类似的响应 - 当然也可以。

          【讨论】:

            【解决方案6】:

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

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

            但是,当制作具有复杂逻辑的东西或尝试排除故障或增强现有的一系列回调时,您可能会发疯。

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

            如果你做了很多此类工作,请检查一下。

            【讨论】:

              【解决方案7】:

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

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

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

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

              【讨论】:

              • 当然你可以在 Javascript 中设置竞争条件,它是高度异步的!只有一个 UI 线程,但任何加载 javascript 或任何 XMLHTTPRequest 的
              • 他是对的,你不能在没有 I/O 的单线程中出现竞争条件。这里没有竞态条件,使用 setTimeout。 JS 是异步的,但是基于一个单线程的事件循环,所以在堆栈为空之前不会调用事件循环。所以程序的行为是完全确定的,你只需要正确阅读代码的工作流程,你就会知道什么时候会调用事件循环。与因为您有多个并发线程而真正不确定的竞争条件不同。
              • 不正确。您可以有由不同的事件顺序引起的竞争条件。通常这发生在 Java 中的线程上,但竞争条件本质上是关于不确定的事件/状态顺序以及如何适当地处理这些。他所描述的正是一种竞争条件,而上面发布的正确解决方案是使用信号量风格的设计模式。
              • 有人投了我的票。所以,让我们加倍努力。如果您认为这不是竞争条件,因为它不涉及线程,那您就错了。时期。 en.wikipedia.org/wiki/Race_condition 你甚至不需要它是软件,它可以发生在电路上。它在多线程代码中尤其普遍,但绝不需要多个线程。
              • 不,JavaScript 中没有竞争条件。探索 JavaScript 中的(不存在的)竞争条件,以及为什么它们永远不会发生:blog.raananweber.com/2015/06/17/…
              猜你喜欢
              • 1970-01-01
              • 2015-01-30
              • 2010-09-25
              • 2019-06-12
              • 1970-01-01
              • 2020-01-16
              • 2014-04-02
              • 2017-10-04
              相关资源
              最近更新 更多