在 Firefox Restartless Extension 中使用 while 循环是一个很好的等待策略吗?
Is using a while loop a good waiting strategy in a Firefox Restartless Extension?
我有一个引导式扩展,它与 Firefox 的 chrome 部分交互(即甚至在内容加载之前),需要 query an SQLite database 进行一些检查。我更喜欢同步呼叫。但是,由于同步调用在性能方面很糟糕并且可能导致 UI 问题,我需要进行异步数据库调用。
我的用例是这样的:
- 对数据库进行aysnc调用
- 完成后进行进一步处理
现在,可以通过将 'further processing' 部分放在 executeAsync
函数的 handleCompletion
部分中来轻松处理。
但是,我希望 'further processing' 的执行与执行此语句无关,即此数据库查找可能发生也可能不发生。如果它没有很好地发生,那就继续吧。如果是这样,我需要等待。
所以,我正在使用基于标志的策略;我在 handleError
& handleCompletion
回调中设置了一个标志 handleCompletionCalled
到 true
.
在进一步处理部分,我做了一个
while(handleCompletionCalled) {
// do nothing
}
//further processing
这是一个好的策略还是我可以做一些更好的事情(我真的不想为此使用观察者等,因为我的整个扩展中有很多这样的情况,我的代码将充满观察者) ?
使用 while
循环等待是一个严重的 Bad Idea™。如果你这样做,结果将是你挂起 UI,或者至少通过快速 运行 通过屋顶驱动 CPU 使用,尽管你的循环很多次越快越好。1
关于异步编程的要点是,您启动一个操作,然后在 activity 完成或失败后执行另一个函数(回调)。这允许您启动多个操作,或者放弃对整个代码的其他部分的处理。通常,此回调应处理所有依赖于异步操作完成的 activity。回调函数本身不必包含执行其他处理的代码。在完成响应异步操作完成所需发生的事情后,它可以调用另一个函数,如 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 UI 之前完成。
在我的一个附加组件中,我有合理数量的处理应该完成,但对于 Firefox UI 来说,这不是绝对必要的显示给用户。 [请参阅“Performance best practices in extensions”。]因此,为了不延迟 UI,我使用了一个计时器和一个在 Firefox 启动 5 秒后执行的回调。这让 Firefox UI 感觉对用户的响应更快。代码是:
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()
中所做的并不一定包括用户首次激活附加组件之前所需的一切。就我而言,这是在用户按下附加组件的 UI 按钮或通过上下文菜单调用它之前必须完成的所有操作。超时 could/should 更长(例如 10 秒),但为 5 秒,因此我不必在开发过程中等待这么长时间进行测试。请注意,还有 one-time/startup 任务 can/should 只有在用户按下加载项的 UI 按钮后才能完成。
1.这里有一个普遍的编程问题:在某些编程语言中,如果您从不从主代码中放弃处理器,则可能永远不会调用您的回调。在这种情况下,您将锁定在 while
循环中,永远不会退出。
我有一个引导式扩展,它与 Firefox 的 chrome 部分交互(即甚至在内容加载之前),需要 query an SQLite database 进行一些检查。我更喜欢同步呼叫。但是,由于同步调用在性能方面很糟糕并且可能导致 UI 问题,我需要进行异步数据库调用。
我的用例是这样的:
- 对数据库进行aysnc调用
- 完成后进行进一步处理
现在,可以通过将 'further processing' 部分放在 executeAsync
函数的 handleCompletion
部分中来轻松处理。
但是,我希望 'further processing' 的执行与执行此语句无关,即此数据库查找可能发生也可能不发生。如果它没有很好地发生,那就继续吧。如果是这样,我需要等待。
所以,我正在使用基于标志的策略;我在 handleError
& handleCompletion
回调中设置了一个标志 handleCompletionCalled
到 true
.
在进一步处理部分,我做了一个
while(handleCompletionCalled) {
// do nothing
}
//further processing
这是一个好的策略还是我可以做一些更好的事情(我真的不想为此使用观察者等,因为我的整个扩展中有很多这样的情况,我的代码将充满观察者) ?
使用 while
循环等待是一个严重的 Bad Idea™。如果你这样做,结果将是你挂起 UI,或者至少通过快速 运行 通过屋顶驱动 CPU 使用,尽管你的循环很多次越快越好。1
关于异步编程的要点是,您启动一个操作,然后在 activity 完成或失败后执行另一个函数(回调)。这允许您启动多个操作,或者放弃对整个代码的其他部分的处理。通常,此回调应处理所有依赖于异步操作完成的 activity。回调函数本身不必包含执行其他处理的代码。在完成响应异步操作完成所需发生的事情后,它可以调用另一个函数,如 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 UI 之前完成。
在我的一个附加组件中,我有合理数量的处理应该完成,但对于 Firefox UI 来说,这不是绝对必要的显示给用户。 [请参阅“Performance best practices in extensions”。]因此,为了不延迟 UI,我使用了一个计时器和一个在 Firefox 启动 5 秒后执行的回调。这让 Firefox UI 感觉对用户的响应更快。代码是:
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()
中所做的并不一定包括用户首次激活附加组件之前所需的一切。就我而言,这是在用户按下附加组件的 UI 按钮或通过上下文菜单调用它之前必须完成的所有操作。超时 could/should 更长(例如 10 秒),但为 5 秒,因此我不必在开发过程中等待这么长时间进行测试。请注意,还有 one-time/startup 任务 can/should 只有在用户按下加载项的 UI 按钮后才能完成。
1.这里有一个普遍的编程问题:在某些编程语言中,如果您从不从主代码中放弃处理器,则可能永远不会调用您的回调。在这种情况下,您将锁定在 while
循环中,永远不会退出。