【问题标题】:Cancelling a long running process in VB6.0 without DoEvents?在没有 DoEvents 的情况下取消 VB6.0 中长时间运行的进程?
【发布时间】:2013-08-14 18:58:45
【问题描述】:

是否可以在不使用 DoEvents 的情况下取消 VB6.0 中长时间运行的进程?

例如:

for i = 1 to someVeryHighNumber
    ' Do some work here '
    ...

    if cancel then
        exit for
    end if
next

Sub btnCancel_Click()
    cancel = true
End Sub

我假设在“如果取消那么...”之前我需要一个“DoEvents”,有没有更好的方法?好久不见……

【问题讨论】:

    标签: vb6


    【解决方案1】:

    不,你没看错,你肯定想在你的循环中使用 DoEvents。

    如果您将DoEvents 放在主循环中并发现这太慢了处理速度,请尝试调用Windows API 函数GetQueueStatus(比DoEvents 快得多)以快速确定是否有必要调用DoEvents . GetQueueStatus 告诉你是否有任何事件需要处理。

    ' at the top:
    Declare Function GetQueueStatus Lib "user32" (ByVal qsFlags As Long) As Long
    
    ' then call this instead of DoEvents:
    Sub DoEventsIfNecessary()
        If GetQueueStatus(255) <> 0 Then DoEvents
    End Sub
    

    【讨论】:

      【解决方案2】:

      不,您必须使用 DoEvents 否则所有 UI、键盘和计时器事件将在队列中等待。

      您唯一能做的就是每 1000 次迭代调用一次 DoEvents。

      【讨论】:

      • 如果您想快速取消,您需要在每次迭代时调用 DoEvents。
      【解决方案3】:

      “for”循环是否在 GUI 线程中运行?如果是这样,是的,您将需要一个 DoEvents。您可能希望使用单独的线程,在这种情况下不需要 DoEvents。你can do this in VB6(不简单)。

      【讨论】:

      • 我认为您被否决了,因为您说他“最好使用单独的线程”。
      【解决方案4】:

      您可以在单独的线程上启动它,但在 VB6 中,这是非常痛苦的。 DoEvents 应该可以工作。这是一个 hack,但 VB6 也是如此(这里有 10 年的 VB 老手,所以不要贬低我)。

      【讨论】:

      • 在 VB6 中使用 CreateThread 有一些含义,在某些情况下,这些含义太致命了,无法考虑。它确实使 VB 运行时处于一种在您不期望的情况下可能会崩溃的状态。所以我不使用它。好吧,我是,但只是为了运行不调用任何运行时函数的即时生成的微型 asm procs。
      • 我应该说“......但在 VB6 中这是你能做的最疯狂的事情。”
      • 是的,VB6 中线程的双重、三重、四重警告。您可以让演示应用程序运行,但仅此而已。
      【解决方案5】:

      将长期运行的任务划分为 quanta。此类任务通常由一个简单的循环驱动,因此将其分成 10、100、1000 次等迭代。使用 Timer 控件,每次触发时都会执行部分任务并随时保存其状态。首先,设置初始状态并启用定时器。完成后,禁用计时器并处理结果。

      您可以通过更改每个量程完成的工作量来“调整”它。在 Timer 事件处理程序中,您可以检查“取消”并根据需要提前停止。您可以通过将工作负载和 Timer 捆绑到带有 Completed 事件的 UserControl 中来使这一切变得更整洁。

      【讨论】:

      • 这里唯一的麻烦是您还必须禁用除“取消”按钮之外的所有其他内容。 +1,因为这直接回答了问题,展示了一种在没有 DoEvents 的情况下允许取消的方法,并且不建议(恐怖!)VB6 中的线程......
      【解决方案6】:

      当我需要它时,这对我很有效。它检查用户是否按下了退出键以退出循环。

      请注意,它有一个非常大的缺点:它会检测用户是否在任何应用程序上按下了转义键 - 而不仅仅是您的应用程序。但是当你想给自己一种方法来中断一个长时间运行的循环,或者一种按住 shift 键绕过一些代码的方法时,这是一个很好的开发技巧。

      Option Explicit
      
      Private Declare Function GetAsyncKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
      
      Private Sub Command1_Click()
          Do
              Label1.Caption = Now()
              Label1.Refresh
              If WasKeyPressed(vbKeyEscape) Then Exit Do
          Loop
      
          Label1.Caption = "Exited loop successfully"
      
      End Sub
      
      Function WasKeyPressed(ByVal plVirtualKey As Long) As Boolean
          If (GetAsyncKeyState(plVirtualKey) And &H8000) Then WasKeyPressed = True
      End Function
      

      GetAsyncKeyState 的文档在这里:

      http://msdn.microsoft.com/en-us/library/ms646301(VS.85).aspx

      【讨论】:

        【解决方案7】:

        这是一个非常标准的 VB6 异步后台处理方案。 (例如,它在 Dan Appleman 的 book 和 Microsoft 的 VB6 samples 中。)您创建一个单独的 ActiveX EXE 来完成这项工作:这样工作就自动在另一个线程上,在一个单独的进程中(这意味着您不需要不得不担心变量被践踏)。

        • VB6 ActiveX EXE 对象应该公开一个事件 CheckQuitDoStuff()。这需要一个名为 Quit 的 ByRef 布尔值。
        • 客户端在 ActiveX EXE 对象中调用 StartDoStuff。此例程在隐藏表单上启动 Timer 并立即返回。这会解除阻塞调用线程。 Timer 间隔非常短,因此 Timer 事件会快速触发。
        • Timer 事件处理程序禁用 Timer,然后回调到 ActiveX 对象的 DoStuff 方法。这开始了冗长的处理。
        • DoStuff 方法会定期引发 CheckQuitDoStuff 事件。客户端的事件处理程序检查特殊标志并在需要中止时设置 Quit True。然后 DoStuff 中止计算并在 Quit 为 True 时提前返回。

        这种方案意味着客户端实际上不需要是多线程的,因为调用线程在“DoStuff”发生时不会阻塞。棘手的部分是确保 DoStuff 以适当的时间间隔引发事件 - 太长,并且您无法在您想要的时候退出:太短,并且您不必要地减慢了 DoStuff。此外,当 DoStuff 退出时,它必须卸载隐藏的表单。

        如果 DoStuff 在中止之前确实设法完成了所有工作,您可以引发不同的事件来告诉客户端工作已完成。

        【讨论】:

          【解决方案8】:

          编辑turns out MSDN 文章有缺陷,技术DOESN'T WORK :(

          这是一篇关于使用 .NET BackgroundWorker 组件在 VB6 中的另一个线程上运行任务的文章。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-09
            • 1970-01-01
            • 2013-01-21
            • 1970-01-01
            • 1970-01-01
            • 2012-04-29
            相关资源
            最近更新 更多