【问题标题】:How to speed up shell function in VBA for Mac如何在 VBA for Mac 中加速 shell 函数
【发布时间】:2017-12-23 05:49:17
【问题描述】:

我一直在使用这个问题的最佳答案中的函数在 VBA(execShell 函数)中运行 shell 脚本,它使用 libc.dylib:

VBA Shell function in Office 2011 for Mac

函数内部有一个while循环,它的运行速度比popen慢得多。我猜这部分是在 shell 中执行的,popen 正在初始化,但我不太明白它在做什么。

Time taken to run popen:
0.01171875 seconds
Time taken to run while loop:
0.8710938 seconds

我想知道如果函数执行需要很长时间,是否可以中止或超时?有没有可以用来处理多个函数调用的方法?我在 shell 脚本中为 curl 命令添加了超时。 "curl --connect-timeout 5 --max-time 10 --retry 2 --retry-delay 0 --retry-max-time 40 ",并且在每次通话前还增加了 0.1 秒的延迟。如果它运行顺利,那将不是问题,但有时它似乎会完全锁定。如果 curl 请求有很长的延迟,我宁愿中止它并继续执行脚本。也许在连续几次失败后完全终止脚本。我认为函数调用将被线性处理,但我认为它可能会同时处理 shell 脚本,这可能会导致问题。理想情况下,我希望获得有关脚本状态的反馈,我尝试使用状态栏来实现该脚本,但是由于 2011 版 Excel 的限制,这不起作用。在运行开始时,我可以看到工作表正在更新并且一切运行顺利,但稍后在脚本中,excel 会冻结,直到它完成。

另一种解决方案是使用 MacScript 命令,但是这个 shell 脚本包含带有“\”和多个引号的正则表达式,并且很难翻译成 applescript,所以我更喜欢使用这种方法,因为它已经工作了。使用 system() 命令不是一个选项,因为 VBA 脚本需要输出。

    Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
    Private Declare Function pclose Lib "libc.dylib" (ByVal file As Long) As Long
    Private Declare Function fread Lib "libc.dylib" (ByVal outStr As String, ByVal size As Long, ByVal items As Long, ByVal stream As Long) As Long
    Private Declare Function feof Lib "libc.dylib" (ByVal file As Long) As Long

    Function execShell(command As String, Optional ByRef exitCode As Long) As String
        Dim file As Long
        file = popen(command, "r")

        If file = 0 Then
            Exit Function
        End If

        While feof(file) = 0
            Dim chunk As String
            Dim read As Long
            chunk = Space(50)
            read = fread(chunk, 1, Len(chunk) - 1, file)
            If read > 0 Then
                chunk = Left$(chunk, read)
                execShell = execShell & chunk
            End If
        Wend

        exitCode = pclose(file)
    End Function

【问题讨论】:

    标签: excel vba macos shell curl


    【解决方案1】:

    popen 运行速度更快,因为它是用 C/C++ 编写的并且是二进制可执行文件。它在操作系统级别运行,而不是在 VBA 虚拟机 (VBA VM) 中运行。您的 While 循环和 VBA 中的所有其他内容都是在 VBA VM 中运行的脚本语言,它必须将您的脚本转换为 p 代码,然后由 VBA VM 执行。如果您有兴趣,维基百科上的 “Visual Basic for Applications” 文章会提供更详细的解释。

    VBA 代码总是比 Excel 对象或popen 等外部函数提供的任何方法慢。例如,在使用值循环填充范围时,使用范围对象自动填充方法总是比 VBA 更快。

    在代码中添加“超时” 的最佳方法是在 while 循环中添加 VBA 方法 DoEvents。那么

    我会将它放在End IfWend 之间,如下所示:

            End If
        VBA.DoEvents
    Wend
    

    这将导致While 循环允许 Excel 和 Windows 处理事件。除了 VBA Shell() 函数,VBA 代码是串行执行的。如果没有VBA.DoEvents,您的代码将继续执行,直到完成或出错。这个Stack Overflow question thread 提供了很好的解释。

    可以加快代码速度的一件事是使用此命令Application.ScreenUpdating = False 关闭屏幕更新。请记住在子末尾使用Application.ScreenUpdating = True 将其重新打开。我会将它放在While 循环中,如下所示:

    Application.ScreenUpdating = False
    While feof(file) = 0 
    .
    .
    .
    Wend
    Application.ScreenUpdating = True
    

    此外,如果您真的很喜欢冒险,您可以编写一个事件处理程序。 Chip Pearson 有一篇很棒的文章 “Events and Event Procedures in VBA”

    【讨论】:

    • 感谢您提供翔实的回答。我现在已将 DoEvents 添加到循环中。经过进一步调试,我发现问题不是我的脚本太慢,而是服务器在一定时间后阻塞请求,几分钟后阻塞被删除,然后脚本继续.我已将等待从 0.1 秒更改为 0.5 秒,这已经解决了该部分,但如果超时,我需要为 curl 设置一个错误。我还将连接超时从 5 秒更改为这对于某些请求来说是不够的,但对于大多数请求来说,它们不到 1 秒。
    猜你喜欢
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-01
    • 2016-03-01
    相关资源
    最近更新 更多