代码之家  ›  专栏  ›  技术社区  ›  Dac0d3r

由于竞争条件,未保留MobX操作计划/执行顺序

  •  1
  • Dac0d3r  · 技术社区  · 6 年前

    以下是我目前所掌握的一个运行示例:

    https://codesandbox.io/s/github/BruceL33t/mobx-action-synchronous-execution-order/tree/master/

    百货商店js公司 :

    import { observable, action } from "mobx";
    import Sensor from "../models/Sensor";
    
    export default class RootStore {
      @observable sensors = new Map();
    
      constructor() {
        let self = this;
        const sensorIds = [
          "sensor1",
          "sensor2",
          "sensor3",
          "sensor4",
          "sensor5",
          "sensor6",
          "sensor7",
          "sensor8",
          "sensor9",
          "sensor10"
        ];
    
        for (let sensor of sensorIds) {
          self.sensors.set(sensor, new Sensor(5));
        }
    
        // setInterval simulates some incoming data (originally from SignalR, and roughly each second)
        setInterval(function() {
          let out = {};
          const x = +new Date(); // unix timestamp
          for (let sensor of sensorIds) {
            const y = Math.floor(Math.random() * 10000) + 1;
            const m = { x: x, y: y };
            out[sensor] = m;
          }
    
          self.addMeasurement(out); // the problem starts here.
        }, 1000);
      }
    
      // the problem!
      @action
      addMeasurement(sensorMeasurementMap) {
        let self = this;
        // this timeout is to try and simulate a race condition
        // since each measurement is incoming each second,
        // here some of them will take as long as 6 seconds to add,
        // due to the timeout.
        // the point is that they should always be added,
        // in the order they were called in.
        // so if the first measurement takes 20 seconds to be added,
        // the next measurements that were received on 2, 3, 4, 5..., 19th second etc,
        // should all "wait" for the prev measurement, so they're added
        // in the right order (order can be checked by timestamp, x)
        setTimeout(() => {
          const keys = self.sensors.keys();
    
          if (keys.length === 0) {
            // never really gonna happen, since we already set them above
          } else {
            for (const key in sensorMeasurementMap) {
              if (self.sensors.keys().indexOf(key) > -1) {
                self.sensors.get(key).add(sensorMeasurementMap[key]);
              } else {
                // also not gonna happen in this example
              }
            }
          }
        }, Math.floor(Math.random() * 20 + 1) * 1000);
      }
    }
    

    传感器js公司 :

    import Queue from './Queue';
    import {observable, action} from 'mobx';
    
    export default class Sensor {
      @observable queue;
    
      constructor(n) {
        this.n = n;
        this.queue = new Queue(this.n);
      }
      @action add(measurement) {
        this.queue.add(measurement);
      }
    }
    

    队列js公司 :

    import {observable, action} from 'mobx';
    
    export default class Queue {
      @observable data;
      constructor(maxSize) {
        this.maxSize = maxSize;
        this.size = 0;
        this.data = [];
      }
    
      @action add(measurement) {
        let removedItem = undefined;
        if(this.size >= this.maxSize) {
          let temp = this.data[0];
          removedItem = temp && temp.y ? temp.y+'' : undefined;
          this.data.shift();
        }
    
        this.data.push(measurement);
    
        if (removedItem === undefined && this.size < this.maxSize) {
          this.size++;
        }
    
        return removedItem;
    
      }
    }
    

    代码中有一些注释,但您绝对需要查看输出 https://codesandbox.io/s/github/BruceL33t/mobx-action-synchronous-execution-order/tree/master/ 去理解它。

    让我在这里解释一下,这是怎么回事。

    这基本上是实际应用程序的一部分的过度简化版本,其中setInterval只是用来模拟信号器事件处理程序,以指示每秒传入的数据。传入数据是我们在addMeasurement操作上方的setInterval函数中创建的数据。

    因此,假设每秒接收到一些传入数据,我们希望将其添加到存储上的可观察地图传感器中。由于这些数据用于在实际应用程序中绘制图表,因此我们需要确保确实按照调用操作的顺序添加这些数据,无论操作需要多长时间才能完成。

    在实际应用程序中,我发现数据被推送到MobX状态的顺序存在一些不一致,因此我将其隔离并将相关部分提取到这个示例中,并试图通过在addMeasurement操作中使用setTimeout函数来夸大它。

    由于每秒都会获取每个数据,但有些测量可能需要20秒才能获取(这并不现实,但可以清楚地显示竞争条件问题),正如现在的代码所示,我们经常会遇到这样的情况:

    [
        {"x":1519637083193,"y":4411},
        {"x":1519637080192,"y":7562},
        {"x":1519637084193,"y":1269},
        {"x":1519637085192,"y":8916},
        {"x":1519637081192,"y":7365}
    ]
    

    这真的不应该发生,因为 1519637083193 大于/晚于 1519637080192 .

    当从这些数据中绘制图表并在之后进行排序时,这是一个真正的问题,因为成本太高,所以我正在寻找一种方法来改进此代码,以便我们可以相信每个addMeasurement只有在前面的操作完全完成后才会触发。或者至少是一种以正确的顺序更新MobX状态的方法

    希望它有意义。

    1 回复  |  直到 6 年前
        1
  •  1
  •   mweststrate    6 年前

    应全部“等待”上一次测量,以便按正确的顺序添加(可以通过时间戳x检查顺序)。

    你能详细说明一下吗?人们怎么知道将来不会收到比当前时间戳大的时间戳,因此会无限期地等待呢?您正在寻找的不就是对测量数组的排序插入(而不是等待)?

    如果排序插入不能解决问题,我可能会执行以下操作(未测试):

    lastAddition = Promise.resolve() // start with already finishied addition
    
    addMeasurement(sensorMeasurementMap) {
          this.lastAddition = this.lastAddition.then(() => {
              return new Promise((resolve, reject) => {
                setTimeout(action(() => {
                    const keys = self.sensors.keys();
    
                    if (keys.length === 0) {
                        // never really gonna happen, since we already set them above
                    } else {
                        for (const key in sensorMeasurementMap) {
                        if (self.sensors.keys().indexOf(key) > -1) {
                            self.sensors.get(key).add(sensorMeasurementMap[key]);
                        } else {
                            // also not gonna happen in this example
                        }
                        }
                    }
                    resolve()
                }), Math.floor(Math.random() * 20 + 1) * 1000);
            })
          })
      }
    }
    

    N、 注意我搬家了 action 在内部,因为您需要在实际修改状态的地方使用它,而不是在计划发生的地方