Firefox Restartless Extension - Является ли использование цикла while хорошей стратегией ожидания?
У меня есть загрузочное расширение, которое взаимодействует с Chrome-частью Firefox (т.е. даже до загрузки контента), и мне нужно запросить базу данных SQLite для некоторой проверки. Я бы предпочел синхронизировать вызов. Но, поскольку синхронный вызов является плохим с точки зрения производительности и может вызвать возможные проблемы с пользовательским интерфейсом, мне нужно сделать асинхронный вызов БД.
Мой вариант использования такой:
- Сделайте aysnc вызов к базе данных
- После завершения сделайте дальнейшую обработку
Теперь это можно легко сделать, разместив часть "дальнейшей обработки" в handleCompletion
часть executeAsync
функция.
Но я хочу, чтобы "дальнейшая обработка" выполнялась независимо от того, выполняется ли этот оператор, т.е. этот поиск в БД может происходить или не происходить. Если это не произойдет хорошо, продолжайте. Если это так, мне нужно подождать. Итак, я использую стратегию, основанную на флаге; Я установил флаг handleCompletionCalled
в handleError
& handleCompletion
обратный звонок в true
,
В дальнейшей части обработки, я делаю
while(handleCompletionCalled) {
// do nothing
}
//further processing
Это хорошая стратегия, или я могу сделать что-то лучше (я действительно не хочу использовать Observers и т. Д. Для этого, поскольку у меня много таких случаев во всем моем расширении, и мой код будет заполнен Observers)?
1 ответ
Используя while
цикл ожидания - это серьезно плохая идея ™. Если вы это сделаете, результатом будет то, что вы повесите пользовательский интерфейс или, как минимум, увеличите загрузку ЦП, быстро выполняя хотя бы ваш цикл большое количество раз как можно быстрее. 1
Суть асинхронного программирования заключается в том, что вы запускаете действие, а затем другая функция, обратный вызов, выполняется после завершения действия или сбоя. Это позволяет вам запустить несколько действий или отказаться от обработки какой-либо другой части общего кода. В общем, этот обратный вызов должен обрабатывать все действия, которые зависят от завершения асинхронного действия. Функция обратного вызова сама по себе не должна включать код для выполнения другой обработки. После выполнения того, что должно произойти в ответ на завершение асинхронного действия, он может просто вызвать другую функцию, например doOtherProcessing()
,
Если вы запускаете несколько асинхронных действий, вы можете ждать завершения всех из них, имея флаги для каждой задачи и одну функцию, которая вызывается в конце всех различных функций обратного вызова, таких как:
function continueAfterAllDone(){
if(task1Done && task2Done && task3Done && task4Done) {
//do more processing
}else{
//Not done with everything, yet.
return;
}
}
Это может быть расширено до произвольного числа задач с использованием массива или очереди задач, которую функция затем проверяет, чтобы убедиться, что все они выполнены, а не жестко закодированный набор задач.
Ожидание:
Если вы собираетесь использовать другой путь обработки, который выполняется, но затем должен дождаться завершения асинхронного действия, вы должны выполнить ожидание, установив таймер или интервал. Затем вы отдаете процессор на указанный период времени, пока не проверите еще раз, чтобы проверить, были ли выполнены условия, которые необходимо выполнить.
В загружаемом дополнении вам, вероятно, потребуется использовать nsITimer
интерфейс для реализации тайм-аута или таймера интервала. Это необходимо, потому что во время выполнения кода инициализации возможно, что нет <window>
существует (т. е. не может быть возможности иметь доступ к window.setTimeout()
).
Если вы собираетесь реализовать ожидание какой-то другой задачи, вы можете сделать это примерно так:
const Cc = Components.classes;
const Ci = Components.interfaces;
var asyncTaskIsDone = false;
var otherProcessingDone = false;
// Define the timer here in case we want to cancel it somewhere else.
var taskTimeoutTimer;
function doStuffSpecificToResultsOfAsyncAction(){
//Do the other things specific to the Async action callback.
asyncTaskIsDone = true;
//Can either call doStuffAfterOtherTaskCompletesOrInterval() here,
// or wait for the timer to fire.
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
function doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval(){
if(asyncTaskIsDone && otherProcessingDone){
if(typeof taskTimeoutTimer.cancel === "function") {
taskTimeoutTimer.cancel();
}
//The task is done
}else{
//Tasks not done.
if(taskTimeoutTimer){
//The timer expired. Choose to either continue without one of the tasks
// being done, or set the timer again.
}
//}else{ //Use else if you don't want to keep waiting.
taskTimeoutTimer = setTimer(doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval
,5000,false)
//}
}
}
function setTimer(callback,delay,isInterval){
//Set up the timeout (.TYPE_ONE_SHOT) or interval (.TYPE_REPEATING_SLACK).
let type = Ci.nsITimer.TYPE_ONE_SHOT
if(isInterval){
type = Ci.nsITimer.TYPE_REPEATING_SLACK
}
let timerCallback = {
notify: function notify() {
callback();
}
}
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(timerCallback,delay,type);
return timer;
}
function main(){
//Launch whatever the asynchronous action is that you are doing.
//The callback for that action is doStuffSpecificToResultsOfAsyncAction().
//Do 'other processing' which can be done without results from async task here.
otherProcessingDone = true;
doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}
Код инициализации при запуске Firefox:
Приведенный выше код изменен по сравнению с тем, что я использую для задержки некоторых действий при запуске, которые не нужно выполнять перед отображением пользовательского интерфейса Firefox.
В одном из моих дополнений у меня есть разумный объем обработки, который необходимо выполнить, но который не является абсолютно необходимым для отображения пользовательского интерфейса Firefox. См. " Рекомендации по повышению производительности в расширениях ".] Таким образом, чтобы не задерживать пользовательский интерфейс, я использую таймер и обратный вызов, который выполняется через 5 секунд после запуска Firefox. Это позволяет пользовательскому интерфейсу Firefox чувствовать себя более отзывчивым к пользователю. Код для этого:
const Cc = Components.classes;
const Ci = Components.interfaces;
// Define the timer here in case we want to cancel it somewhere else.
var startupLaterTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
function startupLater(){
//Tasks that should be done at startup, but which do not _NEED_ to be
// done prior to the Firefox UI being shown to the user.
}
function mainStartup(){
let timerCallback = {
notify: function notify() {
startupLater();
}
}
startupLaterTimer = startupLaterTimer.initWithCallback(timerCallback,5000
,Ci.nsITimer.TYPE_ONE_SHOT);
}
Обратите внимание, что сделано в startupLater()
не обязательно включает в себя все, что необходимо до того, как пользователь впервые активирует рекламу. В моем случае это все, что нужно сделать до того, как пользователь нажмет кнопку пользовательского интерфейса надстройки или вызовет ее через контекстное меню. Тайм-аут может / должен быть больше (например, 10 с), но составляет 5 с, поэтому мне не нужно так долго ждать тестирования в процессе разработки. Обратите внимание, что существуют также одноразовые / запускаемые задачи, которые можно / нужно выполнять только после того, как пользователь нажал кнопку пользовательского интерфейса надстройки.
1. Общая проблема программирования: в некоторых языках программирования, если вы никогда не получаете процессор из основного кода, ваш обратный вызов может никогда не быть вызван. В таком случае вы просто заперли в while
цикл и никогда не выходить.