【发布时间】:2016-03-24 17:18:42
【问题描述】:
我收到了parallel computing toolbox 的试用版,用于进行一些测试,看看它是如何工作的。
我想要执行的测试之一是看看如何使用此工具箱从 GUI 中运行一些后台处理并报告处理进度。
到目前为止,我已经创建了一个简单的 GUI,其中包含一个用于在后台启动/取消处理的按钮(使用 parfeval)和一个用于报告进度的标签。
一切正常(代码在后台运行,我可以处理后台错误或取消),唯一的问题是在客户端会话中报告后台处理进度:
function [] = TestBackgroundWorker()
%[
% Create interface
fig = figure();
cuo = onCleanup(@()delete(fig));
stState = uicontrol('Parent', fig, 'Units', 'normalized', 'Position', [0.1 0.7 0.8 0.2], 'Style', 'text', 'String', 'Ready');
btnOkCancel = uicontrol('Parent', fig, 'Units', 'normalized', 'Position', [0.1 0.1 0.8 0.5], 'Style', 'pushbutton', 'String', 'Go', 'Callback', @(s,e)onOkCancelClicked(fig));
% Backstore
data = guidata(fig);
data.bgw = [];
data.stState = stState;
data.btnOkCancel = btnOkCancel;
guidata(fig, data);
waitfor(fig);
%]
end
function [] = onBackgroundProgress(fig, ratio, msg)
%[
% Here I would like to 'BeginInvoke' in client thread
% to refresh 'ratio/msg' in the GUI.
% Below code of course doesn't work:
% 1) It is not synchronized with UI thread
% 2) It is executed in a session with no display
data = guidata(fig);
set(data.stState, 'String', sprintf('%f - %s', ratio, msg));
%]
end
function [] = onOkCancelClicked(fig)
%[
% Backstore
data = guidata(fig);
if (~isfield(data, 'bgw'))
data.bgw = [];
end
if (isempty(data.bgw))
% Start background work
set(data.btnOkCancel, 'String', 'Cancel');
data.bgw = parfeval(@doBackgroundWork, 0, @(r, m)onBackgroundProgress(fig, r, m));
guidata(fig, data);
% Wait for error/termination/cancel
while(true)
try
idx = fetchNext(data.bgw, 0.3);
catch err
if (~isempty(err.cause) && (strcmp(err.cause{1}.identifier, 'parallel:fevalqueue:ExecutionCancelled')))
% Error was due to cancelation
uiwait(msgbox('Processing canceled by user!', 'modal'));
set(data.btnOkCancel, 'String', 'Go', 'Enable', 'on');
else
% Error real error (TODO: display it in some way)
uiwait(msgbox('Processing error!', 'modal'));
set(data.btnOkCancel, 'String', 'Go', 'Enable', 'on');
end
data.bgw = [];
guidata(fig, data);
break;
end
if (isempty(idx))
% Still processing => Enable message pump to read GUI events
drawnow limitrate;
else
% Processing done
uiwait(msgbox('Processing done!', 'modal'));
data.bgw = [];
guidata(fig, data);
set(data.btnOkCancel, 'String', 'Go', 'Enable', 'on');
break;
end
end
else
% Cancel background work
set(data.btnOkCancel, 'String', 'Cancelling...', 'Enable', 'off');
cancel(data.bgw);
end
%]
end
function [] = doBackgroundWork(onProgress)
%[
count = 10;
for k = 1:count,
onProgress((k-1)/count, sprintf('Step %i / %i', k, count));
pause(1);
end
%]
end
我很理解这个问题,即回调 onBackgroundProgress 是从没有显示的会话中执行的,所以什么也没有发生(而且它与客户端 GUI 不同步)。
有没有办法从工作人员同步数据并将数据传递到 GUI(在 C# 中我会使用 BeginInvoke)?可能我没有以适当的方式使用工具箱来实现我想要的(似乎更倾向于分布式计算而不是多线程),有没有另一种方法可以用这个工具箱做到这一点? ...
编辑
我修改了我的代码以将 drawnow 替换为 timer 对象(这可行)并尝试使用 labSend 和 labReceive 将 UI 与后台会话同步(这不起作用):
%
% PURPOSE:
%
% Test function to see how to have a responsive GUI while computations
% are running in the background.
%
% SYNTAX:
%
% [] = TestBackgroundWorker();
%
%% --- Main routine
function [] = TestBackgroundWorker()
%[
% Make sure parallel pool is started
gcp();
% Create the interface
% A simple figure with a go/cancel button and a label.
fig = figure();
cuo = onCleanup(@()delete(fig));
stState = uicontrol('Parent', fig, 'Units', 'normalized', 'Position', [0.1 0.7 0.8 0.2], 'Style', 'text', 'String', 'Ready!');
btnStartCancel = uicontrol('Parent', fig, 'Units', 'normalized', 'Position', [0.1 0.1 0.8 0.5], 'Style', 'pushbutton', 'String', 'Start', 'Callback', @(s,e)onOkCancelClicked(fig));
% Backstore for later use
data = guidata(fig);
data.stState = stState;
data.btnStartCancel = btnStartCancel;
guidata(fig, data);
% Wait until figure is closed
waitfor(fig);
%]
end
%% -- Event handler for 'go/cancel' button in the GUI
function [] = onOkCancelClicked(fig)
%[
% Backstore
data = guidata(fig);
if (~isfield(data, 'bgw'))
data.bgw = [];
end
% Depending if background process is running or not
if (isempty(data.bgw))
% Start background work
set(data.btnStartCancel, 'String', 'Cancel');
data.bgw = parfeval(@doBackgroundWork, 0, @(r, m)onBackgroundProgress(fig, r, m));
% Start timer to monitor bgw
data.bgwtimer = timer('ExecutionMode', 'fixedSpacing', 'Period', 0.1, ...
'TimerFcn', @(s,e)onBackgroundCheck(fig, data.bgw));
guidata(fig, data);
start(data.bgwtimer);
else
% Cancel background work
p = gcp('nocreate'); % Make sure parpool is started
if (~isempty(p))
cancel(data.bgw);
end
set(data.btnStartCancel, 'String', 'Cancelling (please wait)...', 'Enable', 'off');
end
%]
end
%% --- Event handlers for monitoring the background worker
function [] = onBackgroundCheck(fig, bgw)
%[
try
idx = fetchNext(bgw, 0.3);
if (isempty(idx)),
% Check for messages from the background worker
if ((numlabs ~= 1) && labProbe)
data = labReceive();
onBackgroundProgress(data{:});
end
else
onBackgroundCompleted(fig);
end
catch err
onBackgroundCompleted(fig, err);
end
%]
end
function [] = onBackgroundCompleted(fig, err)
%[
if (nargin < 2), err = []; end
if (isempty(err))
% Normal completion
uiwait(msgbox('Processing done!', 'Processing', 'help', 'modal'));
elseif (~isempty(err.cause) && (strcmp(err.cause{1}.identifier, 'parallel:fevalqueue:ExecutionCancelled')))
% Error was due to cancelation
uiwait(msgbox('Processing canceled by user!', 'Processing', 'help', 'modal'));
else
% Error real error (TODO: display it in some way)
uiwait(msgbox(sprintf('Processing error: %s', err.message), 'Processing', 'error', 'modal'));
end
data = guidata(fig);
data.bgw = [];
stop(data.bgwtimer);
set(data.stState, 'String', 'Ready!');
set(data.btnStartCancel, 'String', 'Start', 'Enable', 'on');
guidata(fig, data);
%]
end
%% --- Event handler for reporting progression status
function [] = onBackgroundProgress(fig, ratio, msg)
%[
cw = getCurrentWorker();
if (~isempty(cw))
% Were are the background thread so send message to the GUI
% NB: Doing broadcast as I don't know the id of the gui
labBroadcast(labindex, {fig, ratio, msg });
else
% Were are the GUI
data = guidata(fig);
set(data.stState, 'String', sprintf('%f - %s', ratio, msg));
end
%]
end
%% --- Processing to be performed in the background
function [] = doBackgroundWork(onProgress)
%[
count = 15;
for k = 1:count,
onProgress((k-1)/count, sprintf('Step %i / %i', k, count));
pause(1);
end
%]
end
显然labSend 和labReceive 只能发生在工作人员之间,而不能发生在客户端之间……似乎是一条死胡同。
【问题讨论】:
-
我不是 PCT 专家,但我认为这行不通;
parfeval在工作池上异步执行函数。这在本地运行在单独的进程(不是线程)和远程在分布式计算机集群上(想想 MPI)。它旨在在后台启动功能并在结果可用时获取结果(一侧通信),而不是替代线程。我看不到如何从工作人员与主进程通信以发布 UI 更新。我知道labSend/labReceive函数,但我不确定它们是否适合您的程序结构。 -
谢谢@Amro。我更新了我的代码尝试使用
labSend/labReceive,但它也不起作用(请参阅我编辑的帖子......似乎labSend/labReceive只能出现在workers之间,但不能出现在客户端)......绝对工具箱很远更多的是面向分布式而不是面向多线程,所以现在猜测最好保持我的旧样式workaround 有一个响应式GUI。 -
这也是我的印象; PCT 提供的模型非常适合无阻塞地向分布式工作人员“提交作业”(具有如下功能:
createJob、parfeval、batch)最终从返回的承诺/延迟对象中检索结果。另一种方法是parfor、spmd和mapreduce,它们提供数据并行的计算方式。最后是 CUDA 和 GPU 计算……综合考虑,我发现 MATLAB 不适合多线程编程风格(有或没有 PCT 工具箱)。
标签: matlab parallel-processing