取消没有DoEvents的VB6.0中长时间运行的进程?
是否可以在不使用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",还有更好的方法吗?有一阵子了...
解决方案
GUI线程中是否正在运行" for"循环?如果是这样,是的,我们将需要一个DoEvents。我们可能要使用单独的线程,在这种情况下,不需要DoEvents。我们可以在VB6中完成此操作(不简单)。
不,我们必须使用DoEvents,否则所有UI,键盘和Timer事件将在队列中保持等待状态。
我们唯一可以做的就是每1000次迭代调用一次DoEvents。
我们可以在一个单独的线程上启动它,但是在VB6中这是一个皇家难题。 DoEvents应该工作。这是一个hack,但是VB6也是如此(在VB工作了10年的资深人士在这里讲话,所以请不要低调我)。
将长期运行的任务划分为量子。此类任务通常是由一个简单的循环驱动的,因此将其分成10、100、1000等迭代。使用Timer控件,每次触发时,它都会执行一部分任务,并在执行时保存其状态。首先,设置初始状态并启用定时器。完成后,禁用计时器并处理结果。
我们可以通过更改每个量子完成多少工作来"调整"它。在Timer事件处理程序中,我们可以检查"取消"并根据需要提前停止。通过将工作负载和Timer绑定到带有Completed事件的UserControl中,可以使所有工作变得更加整洁。
不,我们说对了,我们绝对希望在循环中使用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
当我需要时,这对我来说效果很好。它检查用户是否按下了退出键以退出循环。
请注意,它有一个很大的缺点:它会检测用户是否在任何应用程序上而不是应用程序上都按下了转义键。但是,当我们想给自己一种中断长时间运行的循环的方法,或者按住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/zh-CN/library/ms646301(VS.85).aspx
编辑发现MSDN文章存在缺陷,并且该技术不起作用:(
这是一篇有关使用.NET BackgroundWorker组件在VB6中的另一个线程上运行任务的文章。
这是用于VB6中异步后台处理的相当标准的方案。 (例如,在Dan Appleman的书和Microsoft的VB6示例中。)我们可以创建一个单独的ActiveX EXE来完成工作:这样,工作将自动在另一个线程上进行,并且处于单独的过程中(这意味着我们不必担心变量被践踏)。
- VB6 ActiveX EXE对象应公开事件CheckQuitDoStuff()。这需要一个称为Quit的ByRef布尔值。
- 客户端在ActiveX EXE对象中调用StartDoStuff。该例程以隐藏形式启动Timer并立即返回。这将取消阻塞调用线程。计时器间隔非常短,因此计时器事件会迅速触发。
- Timer事件处理程序将禁用Timer,然后回调到ActiveX对象DoStuff方法。这开始了冗长的处理。
- DoStuff方法会定期引发CheckQuitDoStuff事件。客户端的事件处理程序检查特殊标志,如果有必要中止,则将其设置为True。然后,DoStuff中止计算,如果Quit为True,则提早返回。
这种方案意味着客户端实际上不需要多线程,因为在" DoStuff"发生时调用线程不会阻塞。棘手的部分是确保DoStuff在适当的时间间隔内引发事件太长,并且我们无法在想要的时候退出:太短,并且会不必要地降低DoStuff的速度。另外,DoStuff退出时,必须卸载隐藏的表单。
如果DoStuff在中止之前确实设法完成了所有工作,则可以引发另一个事件来告诉客户端作业已完成。