最好的解决方案是使用单独的线程(一个用于用户界面,一个用于处理)也许使用并行工具箱。无论如何,这将是相当复杂的同步两者。
所以,这里有一个简单的解决方案,它只依赖于anonymous 函数(在处理块之外委托界面刷新) 和drawnow 函数(强制图形界面处理其消息)。
示例应用程序
要使用的示例应用程序非常基础。它包含:
- 一个设置面板(只有一个设置,处理块的迭代次数)
- 进度条
- 执行/取消按钮
注意:源代码很长(大约 250 行,主要是由于创建 gui),所以我放弃了它here。
GUI 创建并不重要。最重要的部分是处理块、检测代码的匿名函数和对 GUI 事件作出反应的回调。因此,我将仅详细介绍这些论文。
处理块
处理块是一个简单的例程:
function [] = processing(count, instrumentation)
%[
for ki = 1:count,
instrumentation.CheckCancel();
instrumentation.Progress((ki-1)/count);
if (ki > 1), pause(1); end
instrumentation.CheckCancel();
instrumentation.Progress(ki/count);
end
%]
end
唯一的特别之处是它需要一个额外的instrumentation 结构。这个结构有两个字段指向调用者定义的两个anonymous函数(即图形界面)。我们很快就会看到如何。
-
CheckCancel 是负责在用户想要停止处理时引发错误的函数。
-
Progress 是一个委托进度报告的函数。
这里是匿名函数如何连接到图形界面(见代码中的doProcessing子函数):
% Connect instrumentation callbacks with the gui
instrumentation.CheckCancel = @(ratio)onCheckCancel(dlg);
instrumentation.Progress = @(ratio)onProgress(dlg, ratio);
% Perform the processing
processing(settings.NumberOfIterations, instrumentation);
Progress 和 CheckCancel 处理程序
这是 GUI 为Progress 定义的处理程序:
function [] = onProgress(dlg, ratio)
%[
% Update the progress bar value
data = guidata(dlg);
uiprogress(data.handles.pbProgress, ratio);
% Force interface to refresh
drawnow();
%]
end
这很简单,它只是更新进度条控件并强制刷新GUI(提醒一下matlab是单线程的,当前正在执行处理块,所以我们需要强制刷新GUI)。
这是CheckCancel 的处理程序:
function [] = onCheckCancel(dlg)
%[
% Force interface to process its events
drawnow();
% Check 'UserData' has not been modified during events processing
guiState = get(dlg, 'UserData');
if (~isempty(guiState) && ....
strcmp(guiState, 'CancelRequested') || strcmp(guiState, 'CloseRequested'))
error('System:OperationCanceledException', 'Operation canceled');
end
%]
end
同样,这很简单。我们强制 GUI 处理事件(按钮点击等),然后我们读取 UserData 是否被修改为某个预期值。如果是这样,我们会引发异常以停止处理。再次提醒,当前正在执行的代码是处理块。
GUI 事件处理程序
GUI 只有两个有趣的事件。用户要么单击关闭按钮,要么他/她单击执行/取消按钮。
注意:提醒即使 matlab 在执行处理块时被锁定,GUI 事件仍会被处理,因为处理块不时调用drawnow(通过检测委托)。
这里是关闭按钮的代码:
function [] = onCloseRequest(dlg)
%[
% If already in computation mode
if (~isempty(get(dlg, 'UserData')))
% Just indicate close is requested and leave immediatly
set(dlg, 'UserData', 'CloseRequested');
data = guidata(dlg);
set(data.handles.btnGoCancel, 'Enable', 'off', 'String', 'Cancelling...');
return;
end
% Immediate close
delete(dlg);
%]
end
这很简单,如果我们处于运行模式,我们只需发出要停止的信号,否则我们立即关闭对话框。
这是执行/取消按钮的代码:
function [] = onGoCancelClick(dlg)
%[
% If already in computation mode
if (~isempty(get(dlg, 'UserData')))
% Just indicate cancel is requested and leave immediatly
set(dlg, 'UserData', 'CancelRequested');
data = guidata(dlg);
set(data.handles.btnGoCancel, 'Enable', 'off', 'String', 'Cancelling...');
return;
end
% Go into computation mode
[settings, err] = tryReadSettings(dlg);
if (~isempty(err))
waitfor(msgbox(err.message, 'Invalid settings', 'Error', 'Modal'));
else
enterComputationMode(dlg);
err = doProcessing(dlg, settings);
leaveComputationMode(dlg, err);
end
%]
end
有点长,反正都是一样的。如果我们处于运行模式,我们只是表明我们想要停止;否则,界面处于正常模式,我们开始处理。
函数tryReadSettings、enterComputationMode 和leaveComputationMode 只是用于更新界面中的控件并很好地报告错误或取消。
结论
我们设计了一个只依赖drawnow 和anonymous 函数的响应式图形界面。这当然只是一个技巧,更好的解决方案是使用多任务处理。
图形界面是在这里以编程方式创建的。如果使用GUIDE 或在GUI Layout toolbox 的帮助下构建,原理是相同的。
可以进一步改进仪器回调,例如,通过添加Log 字段来报告文本框中的处理详细信息或类似于Log4Net 的某些后端(只有消息级别和消息值)。或者通过为中间结果添加回调。
界面也可以改进或修改,例如为什么在修改设置时不运行处理(即停止任何当前运行,而无需每次手动单击“开始/取消”按钮)。
有很多可能性。在这里,只是提供一些地面应用程序开始(或不...)。