分类: C/C++
2008-08-07 17:40:42
Dim fs As New FileSearch(Me.cboDrives.Text, Me.txtFileSpec.Text, _ Me.chkSearchSubfolders.Checked) AddHandler fs.FileFound, AddressOf EventHandler fs.Execute()FileSearch.Execute 方法建立内部参数并调用私有 FileSearch.Search 方法,该方法调用本身又递归查找符合文件定义要求的 匹配。在 Figure 2 中,Globals 类的 Globals.Cancelled 属性是一个共享的布尔值(Shared Boolean)——点击“Cancel”按钮(如果你得到一个机会的话)则 将这个值设置为 True。因为在基于 Windows 的应用程序中,窗体是单线程的,从该窗体中调用所有代码都运行在一个单线程中,并防止运行时 用户界面的交互操作。
使用第二线程
第二个按钮标签是“Incorrect Async”,其作用是通过在一个后台线程中运行 FileSearch.Execute 方法
以试图解决线程阻塞问题。代码通过首次创建一个与 FileSearch.Execute 匹配的带署名的委托类型来添加这个行为,以便他能引用 FileSearch.Execute
方法:
Private Delegate Function ExecuteDelegate() As ArrayList替代直接调用 FileSearch.Execute 方法,代码的第二个版本创建了一个 ExecuteDelegate 类型实例,它包含对 Execute 方法的引用,并调用该委托的 BeginInvoke 方法以便在第二线程上运行此代码,如下所示:
Dim ed As New ExecuteDelegate(AddressOf fs.Execute) AddHandler fs.FileFound, AddressOf EventHandler ed.BeginInvoke(AddressOf HandleCallback, ed)HandleCallback 过程从传递到这个过程的状态信息中获 取最初的委托实例,并调用 EndInvoke 方法:
'' 异步搜索完成之后,如果你调用 BeginInvoke,那么通常也必须调用 EndInvoke。 Dim ed As ExecuteDelegate = DirectCast(ar.AsyncState, ExecuteDelegate) Dim al As ArrayList = ed.EndInvoke(ar) '' 如果你愿意,可以在这里使用 ArrayList。试着再次运行这个例子,点击“Incorrect Async”按钮,在搜索运行的同时你将看到窗体的用户界面保持 着响应。点击“Cancel Search”,现在可以取消搜索操作了,并且你还可以在代码运行时移动窗体或改变窗体大小。另外,窗体包含一个 Form.Closing 事件的事件 处理程序,确保如果你尝试关闭窗体,代码将首先设置Globals.Cancelled 标志。这样就避免了当窗体从内存中被移出后,.NET运行时试图调用窗体的 HandleCallback 过程时可能发生的不愉快。
'' 在窗体类中: Private Delegate Sub UpdateDisplayDelegate( _ ByVal Text As String, ByVal Value As Long) '' 在 EventHandlerAsync 过程中: '' UpdateDisplay(e.FileFound.FullName, e.Counter) Dim atd As New UpdateDisplayDelegate(AddressOf UpdateDisplay) Me.Invoke(atd, New Object() {e.FileFound.FullName, e.Counter})为了观察这个更安全的行为,再次运行示例,并观察输出窗口中的调试信息。Figure 4 显示了示例输出, 这证明了经过一定的努力,在不破坏规则的前提下,执行从后台线程中调用的代码是可能的。
Dim fs As New FileSearch(Me.cboDrives.Text, Me.txtFileSpec.Text, _ Me.chkSearchSubfolders.Checked) '' 在单独的线程忠启动后台进程。 bgw.RunWorkerAsync(fs)RunWorkerAsync 方法通常在窗体的线程里运行,致使该组件从线程池中抓取一个后台线程, 然后触发 BackgroundWorker 的 DoWork 事件处理程序,它运行在此后台线程中。在这个过程里,你在后台线程中添加自己的异步处理代码。DoWork 事件处理程序可能包含如 Figure 5 所示的代码。(这不是在 demo 程序中实际使用的代码,因为 需要修改 Execute 方法以支持取消操作——后面将有更多关于这个问题的讨论。)
Private Sub bgw_RunWorkerCompleted(ByVal sender As Object, _ ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _ Handles bgw.RunWorkerCompleted '' 后台进程已完成. 这个过程和宿主窗体运行在相同的线程中。 Me.lblResults.Text = "Search complete" End Sub当然,一旦你找到某个文件,你还是看不到实际做事情的代码。在先前的例子中, 将文件名添加到窗体列表框的代码是在 FileSearch.FileFound 事件处理程序中被调用的。BackgroundWorker 组件允许同样的行为,但是不再直接做这项 工作(如前面的“bad”例子)或创建自己的委托并调用这个窗体的 Invoke 方法来运行它,BackgroundWorker 组件以一种优雅的方式来处理这个线程 转换。BackgroundWorker 组件允许你从后台线程中调用它的 ReportProgress 方法,该方法触发其 ProgressChanged 事件 处理例程返回到窗体的线程中。
Private Sub EventHandler( _ ByVal sender As Object, ByVal e As FileFoundEventArgs) '' 某个文件被发现,报告此进程,触发 BackgroundWorker.ProgressChanged 事件: Dim intPercent As Integer = _ CInt((e.Counter - pbStatus.Minimum) Mod pbStatus.Maximum) bgw.ReportProgress(intPercent, e) End Sub每找到一个文件,示例应用程序便用 ProgressChanged 事件更新显示(请看 Figure 6)。
Public Sub UpdateDisplay( _ ByVal Item As String, ByVal Counter As Long, ByVal Value As Integer) '' 将该项添加到列表框,更新状态栏并在标签控件中显示找到的文件数。 lstResults.Items.Add(Item) pbStatus.Value = Value lblCounter.Text = String.Format("{0} file(s) found", Counter) Me.Update() End Sub正如你所看到的,我们没有多花费什么功夫就获得了异步行为——不需要创建委托类型或调用委托实例。然而,仍然有一个没有解决的问题:你怎样取消搜索 操作?理论上说,这很容易。
取消搜索
只要你将 BackgroundWorker 组件的 WorkerSupportsCancellation 属性
设置为 True,你就可以象下面这样调用 BackgroundWorker 的 CancelAsync 方法来取消异步操作:
Private Sub btnCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCancel.Click '' 取消异步操作: bgw.CancelAsync() End Sub正像 WorkerReportsProgress 方法,除非你显式地将 WorkerSupportsCancellation 属性设置为 True, 否则你将无法成功地取消这个操作。(这个版本的示例中,Form.Closing 事件处理程序也调用了 CancelAsync 方法,确保在关闭窗体时,如果 异步操作还在运行,则取消它。)
If bgw.CancellationPending Then e.Cancel = True '' 在离开之前,你可以完成某些清除操作,或就在这里退出: Exit Sub End If然而,在我的示例中,这个简单的解决方案不能工作。文件搜索的所有活动都发生在 FileSearch 类中,并且它是从 BackgroundWorker 的 DoWork 事件处理程序内部通过调用 fs.Execute 而触发的。使用BackgroundWorker 组件的一个缺点就是它很难从用户界面提取工作代码——就是说:为了取消这个操作,你必须 与 BackgroundWorker 组件本身(获取 CancellationPending 属性)以及传递到 DoWork 事件处理程序(设置 Cancel 属性)的 DoWorkEventArgs 对象进行交互。因为 FileSearch 类与用户界面无关,此例子中的代码需要被修正,以使得 FileSearch 类可以“看到”BackgroundWorker 组件和 DoWorkEventArgs 对象。
'' 保持追踪 BackgroundWorker 和 DoWorkEventArgs, '' 所以该代码可以取消并报告进程: Private bgw As System.ComponentModel.BackgroundWorker Private eventArgs As System.ComponentModel.DoWorkEventArgsFileSearch.Execute 方法获得这些值并将它们存入类变量,如 下所示:
Public Function Execute( _ ByVal backGroundWorker As System.ComponentModel.BackgroundWorker, _ ByVal e As System.ComponentModel.DoWorkEventArgs) As ArrayList '' 将引用存储到 BackgroundWorker DoWorkEventArgs 对象: bgw = backGroundWorker eventArgs = e '' 搜索匹配的文件. Search(LookIn) Return alFiles End Function最后,FileSearch.Search 方法使用此类一级变量来确定是否应该取消搜索,并指示 BackgroundWorker 组件 ,它已退出(参见 Figure 7 所示代码)。