新玉ねぎっておいしいですよね。甘くって。
ただ、生で食べる時は、まあまあ薄くきらないと苦くって悲しくなりました。
今回は、みんな大好きGASの
「最大起動時間の壁を超える!」
の巻です。
GASの起動時間の最大値は?
リファレンスより、
「Consumer(多分無課金の人)のScript runtime」は 6minのようですね。
つまり、
一回の処理が6分を超えると、
「起動時間の最大値を超えました」
と表示され処理が途中で終わってしまいます。
(そもそも、6分以上の処理させんなよって感じなんですが、、、)
ググったら、回避方法を書いてくれてる方がおり、とても助かりました。
おかげで、わたしのバカ長い処理も何回かに分けて、
最後まで実行完了することができるようになりました。
参考:http://direction-note.com/gas-beyond-execution-time/
やり方
処理が5分を超える場合は、
トリガーを設定して、1分後に処理を続きからするようにする。
①for文のカウントなどを保持し、正しく続きから処理を再開するために、
GASのスクリプトプロパティでデータを保持させます。
スクリプトのプロパティとは?
ファイル > プロジェクトのプロパティ > スクリプトのプロパティ
にあり、キーバリューでデータを保持できます。
↑ キー:nowRow、バリュー:10のデータを保持させている。
この画面の「行を追加」ボタンよりデータを追加/削除することが可能。
GASにて追加することも可能
let properties = PropertiesService.getScriptProperties(); //スクリプトプロパティの登録 properties.setProperty(key, value); //スクリプトプロパティのバリューを取得 properties.getProperty(key); //return value
②最大処理時間を超える前の5分で処理を中断し、
1分後にトリガーを設定する。
let properties = PropertiesService.getScriptProperties(); let targetArray = ['foo', 'bar', 'baz'....]; //この配列が膨大量の時に有効 /* メイン */ function mainSort() { ... setOutputSheet(); } /* 5分以上かかる可能性のある処理 */ function setOutputSheet(){ let startTime = new Date(); //スクリプトプロパティにトリガーIDを保存するときに使用するkey名 let triggerKey = "trigger"; ... for(let i = 0; i < targetArray.length; i++){ //開始時刻(startTime)と現時点の処理時点の時間を比較する let diff = parseInt((new Date() - startTime) / (1000 * 60)); if(diff >= 5){ ... //トリガー(1分後)を登録する setTrigger(triggerKey, "mainSort"); return; } //todo 5分以上かかる可能性のある処理 } //全て実行終えたらトリガー不要なためを削除する deleteTrigger(triggerKey); } /** *指定したkeyに保存されているトリガーIDを使って、トリガーを削除する * * @param string triggerKey * @return void */ function deleteTrigger(triggerKey) { let triggerId = properties.getProperty(triggerKey); if(!triggerId) return; ScriptApp.getProjectTriggers().filter(function(trigger){ return trigger.getUniqueId() == triggerId; }) .forEach(function(trigger) { ScriptApp.deleteTrigger(trigger); }); properties.deleteProperty(triggerKey); } /** * トリガーを発行する * * @param string triggerKey * @param string funcName * @return void */ function setTrigger(triggerKey, funcName){ //既に同名で保存しているトリガーがあったら削除 deleteTrigger(triggerKey); //1分後にトリガーを登録する let date = new Date(); date.setMinutes(date.getMinutes() + 1); let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId(); Logger.log('setTrigger function_name "%s".', funcName); //あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておく properties.setProperty(triggerKey, triggerId); }
作ってみたやつ
①②を組み合わせると、最大起動時間の壁を超えれるということです!
作ってみた全体はこんな感じです。
※このコード自体の処理は5分超えなかったのでsleep()とかして試してましたっw
こんな感じの横並びを、、、
↓
縦にソートします。
プレーヤーが日々追加されることを想定して、
一年の日数 × 最終的なプレーヤー人数分で縦に並ぶように作ってます。
コード全体(テキトーなので参考程度で! )
let properties = PropertiesService.getScriptProperties(); //★メイン function mainSort() { //プレーヤーマスター配列を作成 let playerMaster = getPlayerMaster(); let playerCount = Object.keys(playerMaster).length //日付の配列を取得 let dataMaster = getDataMaster(); let dataCount = Object.keys(dataMaster).length //日別結果データの取得 let DayDate = getDayDate(playerCount, dataCount); //出力用シート(outputSheet)に日別結果の縦版の値を出力する。 setOutputSheet(playerMaster, dataMaster, DayDate); } /** * 「No」「プレーヤー」を配列で取得する * * @return arrShopMaster = { * 1: 'リュウ', * 2: 'ケン' * }; */ function getPlayerMaster() { let ABcolumnAll = inputSheet.getRange('A2:B101').getValues(); let LastRow = ABcolumnAll.filter(String).length; let arrPlayerMaster = []; //「-」を除く for(let i = 0; i < LastRow; i++){ if(!ABcolumnAll[i][1].match(/\-/)){ arrPlayerMaster[ABcolumnAll[i][0]] = ABcolumnAll[i][1]; } } return arrPlayerMaster; } /** * 日付を配列で取得する * * @return array arrDataMaster */ function getDataMaster() { let rowAll = inputSheet.getRange('C1:ND1').getValues(); let arrDataMaster = []; for(let i = 0; i < yearSum; i++){ arrDataMaster[i] = rowAll[0][i]; } return arrDataMaster; } /** * 日付をキーにその日の結果を格納する。 * * @param intger playerCount * @param intger dataCount * @return array arrRet */ function getDayDate(playerCount, dataCount) { let col = inputSheet.getRange('C2:NQ'+(playerCount+1)).getValues(); let arrRet = []; for(let i = 0; i < playerCount; i++){ arrRet[i] = []; for(let j = 0; j < dataCount; j++){ arrRet[i][j] = col[i][j]; } } return arrRet; } /** * 5分以上かかる可能性のある処理 * * @return void */ function setOutputSheet(playerMaster, dataMaster, DayDate){ //処理の開始時刻を取得 let startTime = new Date(); //sleep(300000); //意図的に(300秒/60秒=)5分スリープさせる //途中経過保存用の変数 let rowKey = "nowRow"; //何行目まで処理したかを保存するときに使用するkey let dateKey = "nowDate"; //yearSumが何行目まで処理したかを保存するkey let playerCountKey = "nowPlayerCount"; //lastRowが何行目まで処理したかを保存するkey let triggerKey = "trigger"; //トリガーIDを保存するときに使用するkey let nowRow = parseInt(properties.getProperty(rowKey)); if(!nowRow) nowRow = 1; //初回処理の時 let lastRow = Object.keys(DayDate).length; let now_i = 0; let nowDate = parseInt(properties.getProperty(dateKey)); if(nowDate){ let now_i = nowDate; properties.deleteProperty(dateKey); } for(let i = now_i; i < yearSum; i++){ let date = dataMaster[i]; let now_j = 0; let nowPlayerCount = parseInt(properties.getProperty(playerCountKey)); if(nowPlayerCount){ let now_j = nowPlayerCount; properties.deleteProperty(playerCountKey); } for(let j = now_j; j < lastRow; j++) { //開始時刻(startTime)と現時点の処理時点の時間を比較する let diff = parseInt((new Date() - startTime) / (1000 * 60)); if(diff >= 5){ //何行まで処理したかなどを「スクリプトのプリロパティ」に保存する properties.setProperty(rowKey, nowRow); properties.setProperty(dateKey, i); properties.setProperty(playerCountKey, j); //トリガー(1分後)を登録する setTrigger(triggerKey, "mainSort"); return; } outputSheet.getRange(nowRow, 1).setValue(date); outputSheet.getRange(nowRow, 2).setValue(playerMaster[j+1]); outputSheet.getRange(nowRow, 3).setValue(DayDate[j][i]); nowRow++; } } //全て実行終えたらトリガーや何行目まで実行したかなどのデータは //不要なためを削除する deleteTrigger(triggerKey); properties.deleteProperty(rowKey); properties.deleteProperty(dateKey); properties.deleteProperty(playerCountKey); } /** * 指定したkeyに保存されているトリガーIDを使って、トリガーを削除する * * @param string triggerKey * @return void */ function deleteTrigger(triggerKey) { let triggerId = properties.getProperty(triggerKey); if(!triggerId) return; ScriptApp.getProjectTriggers().filter(function(trigger){ return trigger.getUniqueId() == triggerId; }) .forEach(function(trigger) { ScriptApp.deleteTrigger(trigger); }); properties.deleteProperty(triggerKey); } /** * トリガーを発行。トリガーを発行した箇所から * * @param string triggerKey * @param string funcName * @return void */ function setTrigger(triggerKey, funcName){ //既に同じ名前で保存しているトリガーがあったら削除 deleteTrigger(triggerKey); let date = new Date(); date.setMinutes(date.getMinutes() + 1); //1分後に再実行 let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId(); Logger.log('setTrigger function_name "%s".', funcName); //あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておくproperties.setProperty(triggerKey, triggerId); }