Multithreading 为什么我在并行调用应用程序脚本API时出现超时和文档丢失错误?
我正在写一个GoogleSheets插件,它将一些数据从一个电子表格复制到另一个电子表格,然后重新格式化。所涉及的数据集通常很大(约10万行),因此为了避免达到6分钟的超时限制,我将数据分成块,然后使用google.script.run客户端调用在每个块上并行运行数据复制功能 在我的样本数据集(约10万行)上,成功复制了前两个要完成的数据块,其余的数据块抛出错误“服务电子表格在访问id为[spreadsheet id]的文档时超时” 下面是它在应用程序脚本仪表板中的外观: 我被超时错误弄糊涂了,因为:Multithreading 为什么我在并行调用应用程序脚本API时出现超时和文档丢失错误?,multithreading,google-apps-script,Multithreading,Google Apps Script,我正在写一个GoogleSheets插件,它将一些数据从一个电子表格复制到另一个电子表格,然后重新格式化。所涉及的数据集通常很大(约10万行),因此为了避免达到6分钟的超时限制,我将数据分成块,然后使用google.script.run客户端调用在每个块上并行运行数据复制功能 在我的样本数据集(约10万行)上,成功复制了前两个要完成的数据块,其余的数据块抛出错误“服务电子表格在访问id为[spreadsheet id]的文档时超时” 下面是它在应用程序脚本仪表板中的外观: 我被超时错误弄糊涂
// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
function dataParamsSuccess(dataParameters) {
// dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
var busHrs = dataParameters[0];
var outputSsUrl = dataParameters[1];
var lastRow = dataParameters[2];
var maxRows = dataParameters[3];
var maxColumns = dataParameters[4];
console.log(maxRows);
console.log(maxColumns);
// Set chunk size
var chunkSize = 5000; // number of rows in chunk
// Determine number of chunks
var numChunks = Math.ceil(lastRow / chunkSize);
var lastChunkSize = lastRow % chunkSize;
if ((numChunks-1) * chunkSize + lastChunkSize == lastRow) {
console.log("Math checks out");
} else {
console.log("oops, check your math");
}
// Generate status message
var statusHtml = numChunks + " chunks to be copied";
for (i=0; i<numChunks; i++) {
var chunkNum = i+1;
var chunkNumStr = chunkNum.toString();
statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
}
document.getElementById("statusMsg").innerHTML = statusHtml;
var startRow = 1;
// Call copyData once for each chunk
for (i=0; i<numChunks; i++) {
var chunkNum = i+1;
var chunkNumStr = chunkNum.toString();
var chunkDivId = "chunk" + chunkNumStr + "Status";
if (chunkNum==numChunks) { // if this is the last chunk, chunk size is smaller
chunkSize = lastChunkSize;
}
var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
google.script.run
.withSuccessHandler(copyChunkSuccess)
.copyData(copyParams);
document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
startRow += chunkSize;
console.log("startRow: " + startRow.toString());
}
// Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}
这个答案怎么样
根据我的经验,即使在使用电子表格服务时,当异步进程发生连续访问时,我也遇到过这样的问题。当时,我使用了锁服务和setTimeout
。但我不确定这种方法是否能解决您的问题。因此,请测试以下修改。在这里,我建议在Google应用程序脚本端使用锁服务,在Javascript端使用setTimeout
。当您的脚本被修改时,它将变成如下所示
function copyData(copyParams) {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
// copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
var chunkNum = copyParams[0];
var chunkSize = copyParams[1];
var startRow = copyParams[2];
var outputSsUrl = copyParams[3];
var lastRow = startRow + chunkSize;
// Get input and output sheets
var dataSheet = SpreadsheetApp.getActiveSheet();
var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
var outputSheet = outputSpreadsheet.getActiveSheet();
// Copy values
var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);
// Logging
var dataSpreadsheetId = dataSheet.getParent().getId();
var outputSpreadsheetId = outputSpreadsheet.getId();
console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
return [chunkNum, startRow, lastRow, "success"];
} catch(e) {
return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
} finally {
lock.releaseLock();
}
}
}
// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
async function dataParamsSuccess(dataParameters) { // <--- Modified
const wait = (s) => new Promise(r => setTimeout(r, s)); // <--- Added
// dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
var busHrs = dataParameters[0];
var outputSsUrl = dataParameters[1];
var lastRow = dataParameters[2];
var maxRows = dataParameters[3];
var maxColumns = dataParameters[4];
console.log(maxRows);
console.log(maxColumns);
// Set chunk size
var chunkSize = 5000; // number of rows in chunk
// Determine number of chunks
var numChunks = Math.ceil(lastRow / chunkSize);
var lastChunkSize = lastRow % chunkSize;
if ((numChunks - 1) * chunkSize + lastChunkSize == lastRow) {
console.log("Math checks out");
} else {
console.log("oops, check your math");
}
// Generate status message
var statusHtml = numChunks + " chunks to be copied";
for (i = 0; i < numChunks; i++) {
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
}
document.getElementById("statusMsg").innerHTML = statusHtml;
var count = 0; // <--- Added
var startRow = 1;
// Call copyData once for each chunk
for (i = 0; i < numChunks; i++) {
count++; // <--- Added
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
var chunkDivId = "chunk" + chunkNumStr + "Status";
if (chunkNum == numChunks) { // if this is the last chunk, chunk size is smaller
chunkSize = lastChunkSize;
}
var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
google.script.run
.withSuccessHandler(copyChunkSuccess)
.copyData(copyParams);
if (count == 10) { // <--- Added
console.log("wait");
await wait(5000);
count = 0;
}
document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
startRow += chunkSize;
console.log("startRow: " + startRow.toString());
}
// Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}
此解决方案的流程如下所示
function copyData(copyParams) {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
// copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
var chunkNum = copyParams[0];
var chunkSize = copyParams[1];
var startRow = copyParams[2];
var outputSsUrl = copyParams[3];
var lastRow = startRow + chunkSize;
// Get input and output sheets
var dataSheet = SpreadsheetApp.getActiveSheet();
var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
var outputSheet = outputSpreadsheet.getActiveSheet();
// Copy values
var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);
// Logging
var dataSpreadsheetId = dataSheet.getParent().getId();
var outputSpreadsheetId = outputSpreadsheet.getId();
console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
return [chunkNum, startRow, lastRow, "success"];
} catch(e) {
return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
} finally {
lock.releaseLock();
}
}
}
// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
async function dataParamsSuccess(dataParameters) { // <--- Modified
const wait = (s) => new Promise(r => setTimeout(r, s)); // <--- Added
// dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
var busHrs = dataParameters[0];
var outputSsUrl = dataParameters[1];
var lastRow = dataParameters[2];
var maxRows = dataParameters[3];
var maxColumns = dataParameters[4];
console.log(maxRows);
console.log(maxColumns);
// Set chunk size
var chunkSize = 5000; // number of rows in chunk
// Determine number of chunks
var numChunks = Math.ceil(lastRow / chunkSize);
var lastChunkSize = lastRow % chunkSize;
if ((numChunks - 1) * chunkSize + lastChunkSize == lastRow) {
console.log("Math checks out");
} else {
console.log("oops, check your math");
}
// Generate status message
var statusHtml = numChunks + " chunks to be copied";
for (i = 0; i < numChunks; i++) {
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
}
document.getElementById("statusMsg").innerHTML = statusHtml;
var count = 0; // <--- Added
var startRow = 1;
// Call copyData once for each chunk
for (i = 0; i < numChunks; i++) {
count++; // <--- Added
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
var chunkDivId = "chunk" + chunkNumStr + "Status";
if (chunkNum == numChunks) { // if this is the last chunk, chunk size is smaller
chunkSize = lastChunkSize;
}
var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
google.script.run
.withSuccessHandler(copyChunkSuccess)
.copyData(copyParams);
if (count == 10) { // <--- Added
console.log("wait");
await wait(5000);
count = 0;
}
document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
startRow += chunkSize;
console.log("startRow: " + startRow.toString());
}
// Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}
流量:
copyData
,如下所示
function copyData(copyParams) {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
// copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
var chunkNum = copyParams[0];
var chunkSize = copyParams[1];
var startRow = copyParams[2];
var outputSsUrl = copyParams[3];
var lastRow = startRow + chunkSize;
// Get input and output sheets
var dataSheet = SpreadsheetApp.getActiveSheet();
var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
var outputSheet = outputSpreadsheet.getActiveSheet();
// Copy values
var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);
// Logging
var dataSpreadsheetId = dataSheet.getParent().getId();
var outputSpreadsheetId = outputSpreadsheet.getId();
console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
return [chunkNum, startRow, lastRow, "success"];
} catch(e) {
return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
} finally {
lock.releaseLock();
}
}
}
// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
async function dataParamsSuccess(dataParameters) { // <--- Modified
const wait = (s) => new Promise(r => setTimeout(r, s)); // <--- Added
// dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
var busHrs = dataParameters[0];
var outputSsUrl = dataParameters[1];
var lastRow = dataParameters[2];
var maxRows = dataParameters[3];
var maxColumns = dataParameters[4];
console.log(maxRows);
console.log(maxColumns);
// Set chunk size
var chunkSize = 5000; // number of rows in chunk
// Determine number of chunks
var numChunks = Math.ceil(lastRow / chunkSize);
var lastChunkSize = lastRow % chunkSize;
if ((numChunks - 1) * chunkSize + lastChunkSize == lastRow) {
console.log("Math checks out");
} else {
console.log("oops, check your math");
}
// Generate status message
var statusHtml = numChunks + " chunks to be copied";
for (i = 0; i < numChunks; i++) {
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
}
document.getElementById("statusMsg").innerHTML = statusHtml;
var count = 0; // <--- Added
var startRow = 1;
// Call copyData once for each chunk
for (i = 0; i < numChunks; i++) {
count++; // <--- Added
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
var chunkDivId = "chunk" + chunkNumStr + "Status";
if (chunkNum == numChunks) { // if this is the last chunk, chunk size is smaller
chunkSize = lastChunkSize;
}
var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
google.script.run
.withSuccessHandler(copyChunkSuccess)
.copyData(copyParams);
if (count == 10) { // <--- Added
console.log("wait");
await wait(5000);
count = 0;
}
document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
startRow += chunkSize;
console.log("startRow: " + startRow.toString());
}
// Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}
HTML和Javascript端:
请修改dataparamsccess
,如下所示
function copyData(copyParams) {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
// copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
var chunkNum = copyParams[0];
var chunkSize = copyParams[1];
var startRow = copyParams[2];
var outputSsUrl = copyParams[3];
var lastRow = startRow + chunkSize;
// Get input and output sheets
var dataSheet = SpreadsheetApp.getActiveSheet();
var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
var outputSheet = outputSpreadsheet.getActiveSheet();
// Copy values
var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);
// Logging
var dataSpreadsheetId = dataSheet.getParent().getId();
var outputSpreadsheetId = outputSpreadsheet.getId();
console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
return [chunkNum, startRow, lastRow, "success"];
} catch(e) {
return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
} finally {
lock.releaseLock();
}
}
}
// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
async function dataParamsSuccess(dataParameters) { // <--- Modified
const wait = (s) => new Promise(r => setTimeout(r, s)); // <--- Added
// dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
var busHrs = dataParameters[0];
var outputSsUrl = dataParameters[1];
var lastRow = dataParameters[2];
var maxRows = dataParameters[3];
var maxColumns = dataParameters[4];
console.log(maxRows);
console.log(maxColumns);
// Set chunk size
var chunkSize = 5000; // number of rows in chunk
// Determine number of chunks
var numChunks = Math.ceil(lastRow / chunkSize);
var lastChunkSize = lastRow % chunkSize;
if ((numChunks - 1) * chunkSize + lastChunkSize == lastRow) {
console.log("Math checks out");
} else {
console.log("oops, check your math");
}
// Generate status message
var statusHtml = numChunks + " chunks to be copied";
for (i = 0; i < numChunks; i++) {
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
}
document.getElementById("statusMsg").innerHTML = statusHtml;
var count = 0; // <--- Added
var startRow = 1;
// Call copyData once for each chunk
for (i = 0; i < numChunks; i++) {
count++; // <--- Added
var chunkNum = i + 1;
var chunkNumStr = chunkNum.toString();
var chunkDivId = "chunk" + chunkNumStr + "Status";
if (chunkNum == numChunks) { // if this is the last chunk, chunk size is smaller
chunkSize = lastChunkSize;
}
var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
google.script.run
.withSuccessHandler(copyChunkSuccess)
.copyData(copyParams);
if (count == 10) { // <--- Added
console.log("wait");
await wait(5000);
count = 0;
}
document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
startRow += chunkSize;
console.log("startRow: " + startRow.toString());
}
// Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}
//此函数是在另一个函数(获取总行数)之后运行的成功处理程序
//从要复制的工作表,然后创建要复制到的新电子表格)完成
异步函数dataparamsccess(dataParameters){//newpromise(r=>setTimeout(r,s))//