在多线程编程中,经常需要创建多个线程来处理不同的任务,例如TCP/IP监听、周期性查询数据库、从互联网下载数据等。在Windows环境中,不能让UI线程处理所有这些任务,因为UI线程非常宝贵,需要响应用户操作,如鼠标和键盘事件,而不是花费时间执行上述任务。如果让UI线程处理这些任务,用户可能会认为应用程序“卡住”了。为了避免这种情况,许多开发者使用定时器控件或System.Timer对象在后台处理一些事情。但使用多个线程是最受欢迎或更专业的处理方式。尽管听起来不错,但存在一个问题:不能直接从不同的线程更新任何UI元素。
让考虑一个非常简单的场景。想运行两个线程,它们简单地将一个变量从0增加到9。每次变量增加时,都想更新表单中的富文本框以显示当前值(如上图所示)。如果在没有另一个线程的情况下这样做,那就很简单了。上图描述了从另一个线程更新UI元素的错误逻辑,它失败了。
如上所述,有几种方法可以处理这个问题。最简单的方法之一是使用定时器控件或System.Timer对象,它们是特殊线程,可以直接更新UI元素。但不能总是将这种技术应用于所有场景。例如,不能有一个定时器来“监听”TCP/IP端口,而应该实现一个线程来连续监听。那么,如何从这个线程更新UI呢?
有多种不同的方法可以做到这一点。在这个论坛上有几篇文章解释了从线程更新UI的许多不同技术。但是,发现了一种更简单的方法,使用容器Form或Control的Invoke方法、Delegate对象和一个类似于Delegate的方法。在浏览对象浏览器中的定义时,Invoke方法的定义引起了注意,并帮助在没有太多复杂性的情况下完成了任务。
Invoke方法是一个在控件的线程上下文中调用指定委托的方法。它允许在UI线程上执行代码,即使当前在另一个线程上。
Public Function Invoke(ByVal method As System.Delegate) As Object
Member of: System.Windows.Forms.Control
Summary: Executes the specified delegate on the thread that owns the control's underlying window handle.
Parameters:
method - A delegate that contains a method to be called in the control's thread context.
Return values:
The return value from the delegate being invoked, or null if the delegate has no return value.
在CallBackThread类中定义了两个委托:
' Delegate for the call back function
Public Delegate Sub CallBackDelegate(ByVal status As String)
' The parameterless Thread function delegate
Public Delegate Sub ThreadFunctionDelegate()
以下是调用UI的代码段:
Public Sub UpdateUI(ByVal msg As String)
If m_BaseControl IsNot Nothing AndAlso m_CallBackFunction IsNot Nothing Then
m_BaseControl.Invoke(m_CallBackFunction, New Object() {msg})
End If
End Sub
以下是线程函数和回调UI的方式:
Private Sub ThreadMethod1()
' Thread 1 simply starts from 0 and counts up to 10
For i As Integer = 1 To 10
objThread1.UpdateUI("1st Thread Ticking " & i)
Threading.Thread.Sleep(500)
Next
End Sub
这个项目是一个简单的例子,它有一个可重用的类,可以激活一个线程并将状态作为字符串发送到UI。通过仅仅改变线程函数和回调函数的实现,就可以修改它以适应任何其他场景。同时,通过改变Delegate和类的UpdateUI方法,可以实现任何高级功能。
发送一个简单的状态文本到UI:
不需要改变CallBackThread类。改变线程函数(例如,Private Sub ThreadMethod1())和回调函数(例如,Private Sub CallBackMethod1(ByVal status As String))以满足需求。
将多个参数发送回UI:
更新以下内容:
调用带参数的线程函数。