多线程编程中的UI更新问题

多线程编程中,经常需要创建多个线程来处理不同的任务,例如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:

更新以下内容:

  • 在CallBackThread类中改变CallBackDelegate(例如,Public Delegate Sub CallBackDelegate(ByVal msg As String, progress As Integer))
  • 在CallBackThread类中改变UpdateUI函数
  • 改变表单/控件中的回调方法实现以匹配回调委托的更改(例如,Private Sub CallBackMethod1(ByVal msg As String, progress As Integer))

调用带参数的线程函数。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485