谷歌UiApp,并发,LockService和兔子洞

时间:2014-10-30 18:40:33

标签: google-apps-script google-apps

我正在开发一种可以可靠地执行单个或多个函数的方法。我已经取得了相当的成功,但是确保调用堆栈完全按批次执行时存在一些问题。该理论仍然不是防水的,但根据我的估计,它应该比它更好。

有3个功能(有一些多余的功能可以删除以进行测试):

  1. asyncFunctionLaunch - 在线调用以将新函数集(key)引入调用堆栈。每个键可以包含一个或多个要调用的函数,每个函数都有一个属性“start”,用于标记函数已被调用,但可能尚未完成执行。为了获得更好的想法,使用getTime()的派生来分配密钥。测试显示偏移添加功能不是必需的,但我只是以防万一...该函数接受以逗号分隔的函数名列表。每次调用函数都会在堆栈中创建一个新键,并表示传递的函数必须是串行执行的。但是,堆栈中的密钥可以按任何顺序执行。

  2. eventExecAsyncFunction - 是一个由复选框valuechange事件触发的回调(感谢@Serge的概念!),这是允许列表成员异步执行的内容

  3. cacheQuickEdit - 我所拥有的库函数的“国内”版本,它调用其他修改userCache的库函数。您可以更改这些调用以进行测试;先例函数大约是3行&学术。

  4. // INTRODUCE A LIST OF FUNCTIONS TO BE EXECUTED ASYNCRONOUSLY
    function asyncFunctionLaunch(Functions, app) {
    
      var lockRef = LockService.getUserLock();
      var bLock = lockRef.tryLock(10000);
    
      app = app || UiApp.getActiveApplication();
    
      Functions = Functions.split(',');
      var vExecList = cacheQuickEdit(CACHEKEY.uiExecAsync);
    
      var iOffset = 1;
      var batchKey = String(new Date().getTime()) + '001';
      batchKey = parseFloat(batchKey);
      var vThisKey = {};
    
      if (typeof vExecList != 'undefined') {
    
        while (vExecList[batchKey]) {
          iOffset++;
          var sPadChar = stringPadLR(iOffset, '0', 3);  // YOU DON'T NEED ALL THIS, A SIMPLE getTime() WILL SUFFICE AS THE KEY INDEX FOR TESTING 
          var batchKey = String(new Date().getTime()) + sPadChar;
          batchKey = parseFloat(batchKey);
        };
    
        for (var iFunc in Functions) {
          if (typeof vThisKey[Functions[iFunc]] == 'undefined') {
    
            vThisKey[Functions[iFunc]] = {
              start: false,
            };
    
          };
        };
    
      } else {
    
        vExecList = {};
    
        for (var iFunc in Functions) {
          vThisKey[Functions[iFunc]] = {
            start: false,
          };
        };
    
      };
    
      vExecList[batchKey] = vThisKey;
      cacheQuickEdit(CACHEKEY.uiExecAsync, vExecList);
    
      var oControl = app.getElementById('chkPreloadTrigger');
      oControl.setValue(false, false);
      oControl.setValue(true, true);
    
      lockRef.releaseLock();
    
      return app;
    };
    
    
    // RECURSIVELY EXECUTE CACHED LIST OF FUNCTIONS ASYNCRONOUSLY
    function eventExecAsyncFunction(e) {
    
      var lockRef = LockService.getUserLock();
      var bLock = lockRef.tryLock(50000);
    
      var sSource = e.parameter.source;
      var vTriggerVal = (e.parameter[sSource] == 'true');    
      //var vTriggerVal = false; // FOR TESTING
    
      var vExecList = cacheQuickEdit(CACHEKEY.uiExecAsync);
    
      var app = UiApp.getActiveApplication();
      var oChkPreload = app.getElementById('chkPreloadTrigger');
    
      if (Object.keys(vExecList).length == 0) {
    
        oChkPreload.setValue(false, false);
        return app;
      };
    
      var sExec = '';
      for (var sKey in vExecList) {
    
        var vExec = vExecList[sKey];
        // TEST FOR KEYS WITH FUNCTIONS BEING EXECUTED
        var bExec = Object.keys(vExec).some(function(el) {
          return (vExec[el].start == true);
        });
    
        if (bExec == false) {
          // RETURN FIRST FUNCTION FROM KEY HAVING NO EXECUTING MEMBERS
          sExec = Object.keys(vExec)[0];
          break;
        };
      };
    
      // NO NON-EXECUTING FUNCTIONS FOUND
      if (sExec == '') {
    
        // WORK FROM START OF KEY LIST   
        loop_exec_list: for (var sKey in vExecList) {
          var vExec = vExecList[sKey];
    
          for (var iExec in vExec) {
    
            if (vExec[iExec].start == true) {
              delete vExecList[sKey][iExec];
    
              if (Object.keys(vExec).length < 1) {
                delete vExecList[sKey];
              };
    
            } else {
    
              sExec = Object.keys(vExec)[0];
              break loop_exec_list;
            };
    
          };
        };
    
      };
    
      var iKeys = Object.keys(vExecList).length;
      if (sExec == '') {
    
        oChkPreload.setValue(false, false);
        lockRef.releaseLock();
    
        return app;
      };
    
      vExecList[sKey][sExec].start = true;
    
      var vNewExec = JSON.stringify(vExecList[sKey]);
      var iFuncs = Object.keys(vExecList[sKey]).length - 1;
      delete vExecList[sKey];
    
      var iOffset = 1;
      var batchKey = String(new Date().getTime()) + '001';
      batchKey = parseFloat(batchKey);
    
      while (vExecList[batchKey]) {
        iOffset++;
        var sPadChar = stringPadLR(iOffset, '0', 3);
        var batchKey = String(new Date().getTime()) + sPadChar;
        batchKey = parseFloat(batchKey);
      };
    
      vExecList[batchKey] = JSON.parse(vNewExec);
      cacheQuickEdit(CACHEKEY.uiExecAsync, vExecList);
    
      if (iFuncs > 0) { // IF THIS KEY CONTAINS MORE FUNCTIONS, TRIGGER CALLBACK AGAIN
        oChkPreload.setValue(false, false);
        oChkPreload.setValue(true, true);
      };
    
      lockRef.releaseLock();
      app = this[sExec](app, e);
    
      return app;
    };
    
    function cacheQuickEdit(sTargetCache, vValue) {
    
      var lockRef = LockService.getUserLock();
      var bLock = lockRef.tryLock(10000);
    
      switch (true) {
        case (typeof vValue == 'undefined'):
    
          var vCache = nnGenericFuncLib.cacheLoadObject(sTargetCache);
    
          if (vCache != null) {
            var vObj = JSON.parse(vCache);
          } else {
            var vObj = {};
          };
    
          lockRef.releaseLock();
          return vObj;
    
          break;
    
        case (vValue == ''):
    
          nnGenericFuncLib.cacheDeleteObject(sTargetCache);
          lockRef.releaseLock();
          return null;
    
          break;
    
        default:
    
          nnGenericFuncLib.cacheSaveObject(sTargetCache, JSON.stringify(vValue), nnGenericFuncLib.CACHE_TIMEOUT);
          lockRef.releaseLock();
          return sTargetCache;
    
          break;
      };
    
      lockRef.releaseLock();
      return false;
    };

    我的理论是回调允许堆栈中的函数并发执行,因为锁在调用之前终止,这意味着可以在第一次返回之前加载另一个函数来执行。我假设每个回调都发生在一个唯一的实例中,并且回调本身内没有冲突。

    该计划的另一个关键部分是使用缓存,允许有序的堆栈执行。计划是将堆栈中第一个键的第一个函数标记为“已启动”,然后将该键移动到堆栈的末尾,然后再调用新的回调实例并最终执行该函数。

    下一次调用再次找到第一个不包含已启动函数的键,并复制上一个进程。

    当所有键都包含已标记为已启动的功能时,代码将再次选择列表中的第一个键,删除标记为已启动的功能(希望已完成),然后按照第一个过程 - 依此类推,直到所有键中的所有功能都被标记,执行和删除。

    我在每个函数中都使用了锁,以防止缓存被提前覆盖或延迟。

    所有这些的基础工作,但我通过(单独)在每次执行回调时缓存堆栈看到的是,年表不一致;这一切都有点'量子',我最终得到了太多的电话,而且没有足够的执行。

    欢迎任何想法/建议!

    - 编辑 - call-stack&amp; amp;屏幕截图完成后的顺序

    enter image description here

1 个答案:

答案 0 :(得分:0)

好吧花了几天时间,多愁眉流咬牙,还有一些脱发,但最后我发现了什么问题。 基本上,LockService在这种情况下完全无用(当你知道如何时很容易),所以我必须编写自己的&#34;并发锁服务&#34;。再加上一天的哀嚎,我终于得到了可靠的结果。

我不会发布我在这里提出的代码,因为在没有上下文的情况下很难准确地表示它,而且,现在我已经知道如何进一步改进它。但是,一旦我确实将它全部分类(并且已经进行了几天的按摩和头发替换疗法),我将在其上写一篇博客。让我知道你对此感兴趣,我稍后会发布更新:)