Main Content

在函数结束后清理

概述

好的编程做法是确保使您的程序环境处于干净的状态,这样不会干扰任何其他程序代码。例如,您可能需要

  • 将为了导入或导出而打开的任何文件关闭。

  • 恢复 MATLAB® 路径。

  • 锁定或取消锁定内存以防止或允许擦除 MATLAB 函数或 MEX 文件。

  • 将您的工作文件夹重新设置为默认状态(如果已做了更改)。

  • 确保全局变量和永久变量处于正确的状态。

为此,MATLAB 提供了 onCleanup 函数。在任何程序中使用此函数时,均可针对该程序建立一个清理例程。当此函数终止时,无论是正常终止、出现错误还是键入了 Ctrl+C,MATLAB 都会自动执行清理例程。

以下语句可针对当前运行的程序建立一个清理例程 cleanupFun

cleanupObj = onCleanup(@cleanupFun);

当程序退出时,MATLAB 查找 onCleanup 类的任何实例并执行关联的函数句柄。生成并激活函数清理的过程涉及以下步骤:

  1. 针对正在开发的程序编写一个或多个清理例程。目前假设它只采用一个此类例程。

  2. 针对该清理例程创建一个函数句柄。

  3. 在某些时候(通常是程序代码的早期),插入对 onCleanup 函数的调用,并传递函数句柄。

  4. 当程序运行时,对 onCleanup 的调用会构造一个清理对象,其中包含步骤 1 中创建的清理例程的句柄。

  5. 当程序结束时,MATLAB 隐式清除属于局部变量的所有对象。这会对您程序中的每个局部对象(包括步骤 4 中构造的清理对象)调用析构函数方法。

  6. 该对象的析构函数方法调用此例程(如果存在)。这会执行恢复您的编程环境所需的任务。

您可以针对程序文件声明任意个清理例程。每次调用 onCleanup 都会为返回的每个清理对象建立一个单独的清理例程。

如果出于某种原因,onCleanup 返回的对象的保留期超过您程序的生命周期,则当您的函数终止时,不会运行与该对象关联的清理例程。清理例程将在毁坏该对象(例如通过清除对象变量)时运行。

您的清理例程绝不应依赖于在该例程范围外定义的变量。例如,这里左侧显示的嵌套函数在执行时不会发生任何错误,而右侧非常相似的函数在执行时会失败,并出现错误 Undefined function or variable 'k'。出现此错误是因为清理例程依赖的变量 k 是在嵌套的清理例程范围之外定义的:

function testCleanup               function testCleanup
k = 3;                             k = 3;
myFun                              obj = onCleanup(@myFun);
    function myFun                     function myFun
    fprintf('k is %d\n', k)            fprintf('k is %d\n', k)
    end                                end
end                                end

退出时清理程序的示例

示例 1 - 退出时关闭打开的文件

当函数 openFileSafely 终止时,MATLAB 将关闭带有标识符 fid 的文件:

function openFileSafely(fileName)
fid = fopen(fileName, 'r');
c = onCleanup(@()fclose(fid));

s = fread(fid);
     .
     .
     .
end

示例 2 - 保留所选文件夹

本示例保留当前文件夹,而不管 functionThatMayError 是否返回错误:

function changeFolderSafely(fileName)
   currentFolder = pwd;
   c = onCleanup(@()cd(currentFolder));

   functionThatMayError;
   end   % c executes cd(currentFolder) here.

示例 3 - 关闭图窗并恢复 MATLAB 路径

本示例扩展 MATLAB 路径以包括 toolbox\images 文件夹中的文件,然后显示其中一个文件夹中的图窗。图窗显示后,清理例程 restore_env 会关闭该图窗并将路径恢复到初始状态。

function showImageOutsidePath(imageFile)
fig1 = figure;
imgpath = genpath([matlabroot '\toolbox\images']);

% Define the cleanup routine.
cleanupObj = onCleanup(@()restore_env(fig1, imgpath));

% Modify the path to gain access to the image file, 
% and display the image.
addpath(imgpath);
rgb = imread(imageFile);
fprintf('\n   Opening the figure %s\n', imageFile);
image(rgb);
pause(2);

   % This is the cleanup routine.
   function restore_env(fighandle, newpath)
   disp '   Closing the figure'
   close(fighandle);
   pause(2)
   
   disp '   Restoring the path'
   rmpath(newpath);
   end
end

运行如下所示的函数。您可以通过比较运行函数前后路径的长度,来验证路径是否已恢复:

origLen = length(path);

showImageOutsidePath('greens.jpg')
   Opening the figure greens.jpg
   Closing the figure
   Restoring the path

currLen = length(path);
currLen == origLen
ans =
     1

检索有关清理例程的信息

在上面所示的示例 3 中,清理例程及调用它所需的数据包含在匿名函数的句柄中:

@()restore_env(fig1, imgpath)

该句柄的详细信息随后包含在 onCleanup 函数返回的对象中:

cleanupObj = onCleanup(@()restore_env(fig1, imgpath));

您可以使用清理对象的 task 属性访问这些详细信息,如下所示。(通过将以下代码正好添加到提示“% This is the cleanup routine.”的注释行之前来修改 showImageOutsidePath 函数)

disp '   Displaying information from the function handle:'
task = cleanupObj.task;
fun = functions(task)
wsp = fun.workspace{2,1}
fprintf('\n');
pause(2);

运行修改后的函数以查看 functions 命令的输出及其中一个 workspace 元胞的内容:

showImageOutsidePath('greens.jpg')

Opening the figure greens.jpg
Displaying information from the function handle:
fun = 
     function: '@()restore_env(fig1,imgpath)'
         type: 'anonymous'
         file: 'c:\work\g6.m'
    workspace: {2x1 cell}
wsp = 
     imageFile: 'greens.jpg'
          fig1: 1
       imgpath: [1x3957 char]
    cleanupObj: [1x1 onCleanup]
           rgb: [300x500x3 uint8]
          task: @()restore_env(fig1,imgpath)

Closing the figure
Restoring the path

使用 onCleanup 与 try/catch

在函数意外终止时运行清理例程的另一种方法是使用 try, catch 语句。但使用此方法时有局限。如果用户通过键入 Ctrl+C 结束程序,MATLAB 会立即退出 try 块,并且不会执行清理例程。清理例程也不会在您正常退出函数时运行。

下面的程序会在出错时进行清理,但对于 Ctrl+C 并不作出响应:

function cleanupByCatch
try
    pause(10);
catch
    disp('   Collecting information about the error')
    disp('   Executing cleanup tasks')
end

try/catch 语句不同,onCleanup 函数不仅会对正常退出程序以及可能引发的任何错误作出响应,还会对 Ctrl+C 做出响应。下一个示例将 try/catch 替换为 onCleanup

function cleanupByFunc
obj = onCleanup(@()...
    disp('   Executing cleanup tasks'));
pause(10);

脚本中的 onCleanup

onCleanup 在脚本中的工作方式与在函数中不同。在函数中,清理对象存储在函数工作区中。当函数退出时,会清空此工作区,因而执行关联的清理例程。在脚本中,清理对象存储在基础工作区中(即用于在命令提示符下完成的交互式工作的工作区)。因为现有脚本对基础工作区没有任何影响,所以不会清除清理对象并且不会执行与该对象关联的例程。要在脚本中使用这种类型的清理机制,您必须在第一个脚本终止时通过命令行或另一个脚本以显式方式清除该对象。