在VB6.0中如何取消长时间运行的进程而不使用DoEvents?

22

有没有可能在VB6.0中在不使用DoEvents的情况下取消一个长时间运行的进程?

例如:

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

    if cancel then
        exit for
    end if
next

Sub btnCancel_Click()
    cancel = true
End Sub

我假设在"if cancel then..."之前需要一个"DoEvents",有更好的方法吗?已经有一段时间没有接触了...

8个回答

30

不错,你说得对,你的循环中确实需要使用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

8

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

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


如果您想快速取消,您需要在每次迭代中调用DoEvents。 - MusiGenesis

7

"for"循环是否在GUI线程中运行?如果是的话,是的,你需要一个DoEvents。你可能想使用一个单独的线程,在这种情况下,不需要DoEvents。你可以在VB6中做到这一点(不简单)。


我想你被负评是因为你说他“最好使用单独的线程”。 - MusiGenesis

5
这是 VB6 中异步后台处理的一个相当标准的方案。(例如在 Dan Appleman 的 书籍 和 Microsoft 的 VB6 示例 中都有。) 您需要创建一个单独的 ActiveX EXE 来完成工作: 这样工作就会自动在另一个线程、另一个进程中进行(这意味着您不必担心变量被践踏)。
  • VB6 ActiveX EXE对象应该公开一个名为CheckQuitDoStuff()的事件。这个事件需要一个名为Quit的ByRef布尔值。
  • 客户端在ActiveX EXE对象中调用StartDoStuff方法。这个方法在隐藏表格上启动一个计时器,并立即返回。这样可以解除调用线程的阻塞。计时器间隔非常短,因此计时器事件很快就会触发。
  • 计时器事件处理程序禁用计时器,然后回调到ActiveX对象的DoStuff方法。这开始了漫长的处理过程。
  • 定期地,DoStuff方法会引发CheckQuitDoStuff事件。客户端的事件处理程序检查特殊标志,并在必要时将Quit设置为True以中止操作。如果Quit为True,则DoStuff会中止计算并提前返回。

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

如果DoStuff在被中止之前确实能够完成所有工作,您可以触发不同的事件来告诉客户端工作已经完成。

4

4

你可以在一个单独的线程上启动它,但在VB6中这很麻烦。使用DoEvents应该可以解决问题。这是一种hack方法,但VB6本身也是如此(我是一名使用VB超过10年的老手,所以请不要对我进行负面评价)。


在VB6中使用CreateThread有一些影响,在某些情况下是太致命而无法考虑的。它会使VB运行时处于一个意想不到的崩溃状态。所以我不使用它。嗯,实际上我还是在使用它,但只用于运行即时生成的小型汇编过程,这些过程不调用任何运行时函数。 - GSerg
我应该说:“...但在VB6中,这可能是你能做的最疯狂的事情。” - MusiGenesis
2
是的,关于VB6中的线程存在双重、三重、四重警告。你可以让示例应用程序工作,但大致就只有这样了。 - Kris Erickson

4
将长时间运行的任务分为多个小段。这些任务通常由简单循环驱动,因此将其分成10、100、1000等迭代。使用定时器控件,每次触发时执行部分任务并保存状态。开始时,设置初始状态并启用定时器。完成后,停用定时器并处理结果。
您可以通过改变每个任务量来“调整”它。在定时器事件处理程序中,您可以检查“取消”并根据需要早停。您可以将工作负载和计时器捆绑到具有“已完成”事件的用户控件中,使其更加整洁。

唯一的麻烦是,您还必须禁用除“取消”按钮以外的所有内容。+1,因为这直接回答了问题,展示了一种允许取消而不使用DoEvents并且不建议(可怕!)在VB6中使用线程的方法... - JeffK

4

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

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

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/zh-cn/library/ms646301(VS.85).aspx


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接