我正在编写一个函数,它做一些工作来向调用方提供结果;但一旦完成了这项工作,就存在一些调用者不需要的后续工作,但已知这些工作将在未来有用。我希望在MATLAB进程不再忙于执行即时需要的任务时执行这项后续工作,这样它就不会减慢或阻碍其他优先级任务的执行。
在前端界面上,MATLAB可以清楚地了解代码当前是否正在执行。当存在时,状态栏读取"0";忙"并且编辑器色带更新以显示"编辑";暂停";按钮中断执行。您可以在命令窗口中输入新命令,但在当前执行的代码完成之前,这些命令不会被读取和执行。一旦执行完成,就清除状态栏中的文本;暂停";按钮被替换为";运行";按钮,并且命令窗口显示>>
,指示在那里输入的新命令将立即执行。
实际上,我希望在函数中间运行的代码能够具有在命令窗口中输入新命令的等效效果,在当前执行的代码完成后运行。
例如,考虑函数:
function testfun(x)
disp("Starting main execution with input " + x);
pause(1)
disp("Completed main execution with input " + x);
disp("Starting followup execution with input " + x);
pause(1)
disp("Completed followup execution with input " + x);
end
一个函数或脚本多次调用它(如果有额外的disp
调用,可能还有其他未指定的耗时活动(:
testfun(1)
disp("Done testfun(1).")
testfun(2)
disp("Done testfun(2).")
将导致输出:
Starting main execution with input 1
Completed main execution with input 1
Starting followup execution with input 1
Completed followup execution with input 1
Done testfun(1).
Starting main execution with input 2
Completed main execution with input 2
Starting followup execution with input 2
Completed followup execution with input 2
Done testfun(2).
总共耗时4秒。另一种解决方案将产生输出:
Starting main execution with input 1
Completed main execution with input 1
Done testfun(1).
Starting main execution with input 2
Completed main execution with input 2
Done testfun(2).
Starting followup execution with input 1
Completed followup execution with input 1
Starting followup execution with input 2
Completed followup execution with input 2
输出Done testfun(2)
是最后一个出现的优先级较高的输出结果,它只需要2秒就可以出现,而不是原来的4秒。
这可能吗?或者,如果我可以允许执行清除当前的执行堆栈(就像我可以用调试器中断代码,然后一直使用dbstep out
到基本工作区,然后从命令窗口调用新代码一样(,这将是一个有用的折衷方案,即使它保留了在基本工作区中有更多函数调用排队的可能性。(代码在其中执行的工作区的实际选择并不重要,但走出更深的嵌套工作区至少可以让这些工作区中排队的剩余代码在工作区被破坏之前完成。(
感谢MathWorks的Jan Simon,我所知道的最好的解决方案是观察MATLAB窗口的实际状态栏。可通过以下方式访问:
statusbar = com.mathworks.mde.desk.MLDesktop.getInstance.getMainFrame.getStatusBar;
然后可以使用statusbar.getText
检索包含在状态栏中的文本。当代码执行时,文本包含单词";"忙";,当什么都不执行时,它就不执行了,所以我可以使用其中的逻辑来判断MATLAB当前正忙(注意其他事情,如探查器,也会修改本文内容的可能性(。
timer
可以轮询该文本,因此在MATLAB的Busy状态结束后,回调将在短时间内启动(尽管不是立即启动,这取决于我愿意轮询接口的力度(。
这并不理想,因为它只是间接地从UI元素推断繁忙状态,而UI元素并非设计用于应用程序控制,并且可能不可靠,而且它依赖于重复轮询,但在某些情况下,这总比什么都没有好。
将此应用于简化示例,我们可以将任务分解为两个函数,其中主要函数使用额外的参数t
来接收要与之交互的计时器对象:
function testmain(x,t)
disp("Starting main execution with input " + x);
pause(1)
disp("Completed main execution with input " + x);
t.UserData.followups(end+1) = x;
end
function testfollowup(x)
disp("Starting followup execution with input " + x);
pause(1)
disp("Completed followup execution with input " + x);
end
计时器的回调可以是:
function followupcallback(t,~)
if isempty(t.UserData.followups)
return
end
status = t.UserData.statusbar.getText;
if ~isempty(status) && contains(string(status),"Busy")
return
end
arrayfun(@testfollowup,t.UserData.followups);
t.UserData.followups = [];
t.stop
end
然后可以设置轮询计时器:
t = timer( ...
"TimerFcn",@followupcallback, ...
"Period",0.25, ...
"ExecutionMode","fixedRate", ...
"UserData",struct( ...
"statusbar",com.mathworks.mde.desk.MLDesktop.getInstance.getMainFrame.getStatusBar, ...
"followups",[]));
t.start;
修改示例脚本以使计时器通过:
testfun(1,t)
disp("Done testfun(1).")
testfun(2,t)
disp("Done testfun(2).")
结果输出将按预期重新排序。
我认为您可以构建一个实际的工作队列。我在这里使用了一个全局变量,这样多个不同的函数就可以添加到工作队列中。我还使用嵌套函数将任意代码和变量封装到一个函数句柄中,该句柄可以存储并在以后运行
我仍然不清楚为什么这会有用,但它确实按照OP中示例给出的顺序处理事情。
initialize_work_queue
testfun(1)
disp("Done testfun(1).")
testfun(2)
disp("Done testfun(2).")
process_work_queue
disp("Done process_work_queue.")
function testfun(x)
disp("Starting main execution with input " + x);
pause(1)
disp("Completed main execution with input " + x);
function follow_up_work
disp("Starting followup execution with input " + x);
pause(1)
disp("Completed followup execution with input " + x);
end
global my_work_queue
my_work_queue{end+1} = @follow_up_work;
end
function initialize_work_queue
global my_work_queue
my_work_queue = {};
end
function process_work_queue
global my_work_queue
while ~isempty(my_work_queue)
func = my_work_queue{1};
my_work_queue(1) = [];
func();
end
end