65.9K
CodeProject 正在变化。 阅读更多。
Home

异步处理 - 基础和 VB6/VB.NET 演练 - I

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.90/5 (30投票s)

2003年11月24日

4分钟阅读

viewsIcon

159062

downloadIcon

4706

异步过程的基础及 VB6 和 .NET 的实现方法

引言

这篇文章源于我对异步处理、其基础和特性的好奇心和偏爱。在本文中,我将尝试涵盖这些方面。

  1. 异步处理的基础和必要性
  2. 同步处理和异步处理的区别
  3. 如何在 VB6 和 .NET 中实现异步处理,并进而了解其在 .NET 中的效率。
  4. 结论

考虑这样一个场景——我想将大量文件传输到某个位置作为我应用程序的一部分。Visual Basic 提供了 API、文件系统对象以及更多工具,通过它们我可以完成这项任务。比如,我可以写 CopyFile <SourceFile> <Destination> 并循环执行以传输文件。是的,这肯定会奏效,并且在传输文件的大小较小时效果很好。当您尝试处理一个大文件或大量文件时,您会注意到在文件传输过程中整个过程会被阻塞。在传输完成之前,您将无法做任何其他事情。如果在后台进行传输,并且您可以在传输过程中执行任何其他操作(例如任何 UI 功能,甚至取消传输操作),那会不会更好呢?这正是异步处理的必要性所在。

上述方法是同步的,主应用程序线程用于此传输,直到操作完成,该线程才会被释放用于任何其他 UI 操作。(即使您因为某种原因想取消文件传输,您也无法做到)。

而实现这种后台处理正是我们关注的重点——异步处理,其中操作在一个独立的线程中执行,而不是调用线程,从而使主线程能够执行任何其他操作。这使用户在处理(如传输文件)时可以做任何事情。而创建的子线程可以 **回调** 主线程,在传输完成或发生错误时通知主线程,应用程序可以据此采取相应行动。

实现

如果您已经经历了 VB 的版本演变,那么您不会否认实现异步处理并不那么直接。VB 5 之后的版本通过使用 ActiveX EXE(ActiveX DLL 的对应物)提供了运行 VB 应用程序多线程的可能性。ActiveX DLL 是 **进程内** 例程,与调用应用程序在同一进程中运行,因此工作是同步的。相反,ActiveX EXE 是 **进程外** 例程,它们在单独的进程中运行,因此工作是异步的。(如果您阅读任何 COM 文章,可以深入了解进程内和进程外组件及其特性)。

在此,我将尝试向您解释在 VB 和 VB.NET 中处理异步处理的实现。我将以一个小例子为例,其中一个例程通过 sleep 事件不必要地造成一些延迟(当然,这是为了在此解释场景)。

使用 VB

正如我们所见,VB 中的异步组件将通过 ActiveX EXE 实现。(必须创建一个 ActiveX EXE 类型的项目来开发 ActiveX EXE 组件)。创建它涉及的基本步骤包括:

  1. 将过程包含在 ActiveX EXE 中,并将其设为私有
  2. 包含一个包装器方法来调用需要异步运行的私有过程。好吧,包装器不仅仅是单独完成这个工作(这与原始方法没有区别),它还包含一个它启用的计时器。当计时器事件触发时,调用原始方法。

注意:我草拟的这个示例基于 vbaccelerator.com 上的示例,我发现这是实现 ActiveX EXE 的最佳方式。我试图详细解释这段代码。

拥有计时器的想法并不意味着我们被迫使用一个可以容纳计时器的窗体。我们也可以使用一个提供计时器功能的类。您可以在 这里 和许多其他网站找到它,但我推荐提到的那个。

此外,没有直接引用模块中的对象实例,而是使用了无错误接口 Runnable,这是一种您可以在 MSDN 的 CodeFlow 示例中找到的类型库。

' This routine will call the method asynchronously. This uses a timer to
' fire the Runnable_Start() implementation, 
' which ensures we yield control back 
' to the caller before the processing starts. 
' This ensures that the processing runs 
' asynchronously to the client.

类模块将如下所示(为便于阅读核心功能,我省略了 API 和其他声明)

cAsync.cls

' This is the wrapper routine, which in turn
' calls the original routine but within a 
' timer. This call would be asynchronous.
' You can call the Runnable_Start() directly 
' which would make the call synchronous.
 
Public Sub Start()
   If Not m_bRunning Then
      m_bRunning = True
      mStart.Start Me
   Else
      ' Just checking....
      Err.Raise 32540, App.EXEName & ".cAsync", "Already running."
   End If
End Sub
 
 
' A routine which makes some delay and 
' raises an event (Callback) to the calling 
' Function when the processing is complete or interrupted.
 
Private Sub Runnable_Start()
   Dim i As Long
   Dim bCancel As Boolean
   
   ' Begin work:
   Do
      SleepAPI 100
      i = i + 1
      RaiseEvent Status(i, bCancel)
   Loop While (i < m_lInterval) And Not bCancel
   
   ' Completion status:
   If i = m_lInterval Then
      ' Success:
      RaiseEvent Complete
   Else
      ' Failure:
      RaiseEvent Cancelled
   End If
   ' All done:
   m_bRunning = False   
End Sub

mStart.bas

(使用了回调接口,它又需要函数的地址,这就迫使我们使用一个提供此选项的模块。)

Private Sub TimerProc(ByVal lHwnd As Long, ByVal lMsg As Long, _
    ByVal lTimerID As Long, ByVal lTime As Long)
   Dim this As Runnable
   ' Enumerate through the collection, firing the
   ' Runnable_Start method for each item in it and
   ' releasing our extra lock on the object:
   With m_colRunnables
      Do While .Count > 0
         Set this = .Item(1)
         .Remove 1
         this.Start
         'Ask the system to release its lock on the object
         CoLockObjectExternal this, 0, 1
      Loop
   End With
   ' Remove the timer:
   KillTimer 0, lTimerID
   m_lTimerID = 0
End Sub
 
 
Public Sub Start(this As Runnable)
   ' Ask the system to lock the object so that
   ' it will still perform its work even if it
   ' is released
   CoLockObjectExternal this, 1, 1
   ' Add this to runnables:
   If m_colRunnables Is Nothing Then
      Set m_colRunnables = New Collection
   End If
   m_colRunnables.Add this
   ' Create a timer to start running the object:
   If Not m_lTimerID Then
      m_lTimerID = SetTimer(0, 0, 1, AddressOf TimerProc)
   End If
End Sub

这是如何在 Visual Basic 6 环境中实现 ActiveX EXE 的一个非常基础的示例。

这是本文的第一部分,解释了异步处理的基础以及如何在 VB6 中实现它。在本文的下一部分,我将提供一个 VB.NET 的解决方案,并解释它有何不同以及开发起来有多么精妙。

© . All rights reserved.