在实际应用中遇到长时间计算分析,让用户等待的时候,窗体如果没有处理好,一般会出现假死状态。网络上也有很多这方面处理的方法,多线程或者BackgroundWorker组件。而且大部分采用了Singleton单一模式方法。
疑问:
1.大部分复杂操作是在后台操作(另外一个线程),由主线程的创建一个子线程来操作。能否在子线程操作的过程中弹出一个等待提示窗体吗?由子线程和等待提示窗体自己完成一切的操作。
正常的做法是:在后台执行工作中,发送信息给主线程UI。
疑问1是本文的重点:某个线程A(可以是主线程或者子线程)创建一个包含有等待提示窗体的线程B,由A发送信息给B。那么线程A的父线程或者主进程就不用理会A和B了。
2.如果有多个子线程在处理分析,那么Singleton单一模式一个窗体来显示等待提示信息的话,一个窗体显示不同几组提示信息,就不大好了,或者提示信息的显示就会乱。
为了解决以上的疑问,参考了本论坛和博客园的几位高人的思路,写了一个允许多个等待提示窗体的程序,本人水平有限,拍砖就拍砖吧,望大家指点更好的方法或思路。
参考链接:
http://www.cnblogs.com/xiaozhi_5638/p/3457499.html
http://www.jb51.cc/article/p-epbfvvyh-bbo.html
正常做法有两种等待模式:引用第一个链接的插图:
图1 等待模式,后台工作没有完成之前,UI界面不允许操作。
图2,非等待模式,后台工作没完成之前,UI界面可以操作。
本文的疑问的处理方法:
图3,简单做法,任务大的话UI线程理论上还是会有阻塞。但可以解决UI空间不足等问题。
图4 推荐做法,不必每次都写更新UI线程的界面,复杂任务执行中自己处理等待提示消息,主UI不必去理会A和B。
如果有多个等待提示窗体,那么就会有多个过程产生多个提示窗体,而且为了让等待提示窗体不阻碍主线程的操作,那么必须将等待提示窗体封装起来。
封装在一个类里面,而且窗体的运行模式应该是在另外一个线程里面。同时等待提示窗体允许创建他的线程发送提示信息给他,等待提示窗体接收到信息后显示出来。
程序主要内容有:
1.一个等待提示窗体:FrmMsgWaitingFor
窗体有几种组合模式:
Public Enum eWaitingFormType Message '只显示提示信息 Progress '只显示进度条 由于进度无法预测,默认设置Stype属性设为Marquee MessageAndProgress '显示信息和进度条 MessageAndWorking '显示信息和加载动画 End Enum
2.一个可以创建等待提示窗体的类似工厂的类:MsgWaitingFactory
此类需要完成的工作:
2.1可以创建出不同的等待提示窗体。
2.2在另外一个线程中创建窗体并显示,让调用本类的线程(可以主线程或者某个子线程)与等待提示窗体线程分离。
2.3主线程可以通过这个类的实例来发送信息给等待提示窗体,也可以通知和关闭等待提示窗体。
调用MsgWaitingFactory方法可以是主线程调用,也可以是子线程调用:也就是上面所说的A和B的调用:
Dim msgWaiter As New MsgWaitingFactory(eWaitingFormType.MessageAndWorking) msgWaiter.Show() msgWaiter.SendMsgToWaiter("开始复杂计算分析...") '...... '复杂计算分析过程中 msgWaiter.SendMsgToWaiter("提示信息...") '...... msgWaiter.SendMsgToWaiter("处理完毕.") msgWaiter.Close()
当然,因为FrmMsgWaitingFor有简单的用跨线程的安全处理,也可以直接调用这个窗体:
Dim frm As New FrmMsgWaitingFor(eWaitingFormType.MessageAndWorking,Me) frm.Show() frm.SetMessage("working...") frm.SetMessage("update UI...") frm.SetMessage("Finish.") frm.Close()
等待提示窗体设计界面如下:
源代码:MsgWaitingFactory
Imports System.Threading Public Class MsgWaitingFactory Private _threadWaiter As Thread Private _frmWaiter As FrmMsgWaitingFor Private _eFormType As eWaitingFormType Private _MyParentForm As Form '------------------------------------------------------ 'Singleton单一模式 'Private Shared ReadOnly objLock As New Object 'Private Shared _curFrm As FrmMsgWaitingFor 'Public Shared ReadOnly Property CurrentWaitingForForm(e As eWaitingFormType) As FrmMsgWaitingFor ' Get ' SyncLock objLock ' If _curFrm Is Nothing Then ' _curFrm = New FrmMsgWaitingFor(e) ' End If ' Return _curFrm ' End SyncLock ' End Get 'End Property '------------------------------------------------------ Public Sub New(e As eWaitingFormType) _eFormType = e End Sub '------------------------------------------------------ Public Sub New(e As eWaitingFormType,ParentForm As Form) _eFormType = e _MyParentForm = ParentForm End Sub Public Sub SendMsgToWaiter(msg As String) If _frmWaiter IsNot Nothing Then _frmWaiter.SetMessage(msg) End Sub Public Sub SendMsgToWaiter(msg As String,isTitle As Boolean) If _frmWaiter IsNot Nothing Then If isTitle Then _frmWaiter.SetFormText(msg) Else _frmWaiter.SetMessage(msg) End If End If End Sub Public Sub Show() If _threadWaiter IsNot Nothing Then Try _threadWaiter.Abort() _threadWaiter = Nothing Catch ex As ThreadStartException End Try End If '--------------------- _threadWaiter = New Thread(New ThreadStart(Sub() _frmWaiter = New FrmMsgWaitingFor(_eFormType) '_frmWaiter.MdiParent = _MyParentForm Application.Run(_frmWaiter) End Sub)) '--------------------- _threadWaiter.IsBackground = True _threadWaiter.SetApartmentState(ApartmentState.STA) _threadWaiter.Start() End Sub Public Sub Close() '----------- 'If _threadWaiter IsNot Nothing Then ' Try ' 'Application.OpenForms("FrmMsgWaitingFor").Close()跨线程有异常 ' 'Application.OpenForms("FrmMsgWaitingFor").Dispose() ' _threadWaiter.Abort() '终止线程,也有异常。 ' _threadWaiter.DisableComObjectEagerCleanup() ' Catch ex As ThreadStartException ' End Try 'End If '--此方法让窗体自动执行在其线程上关闭 If _frmWaiter IsNot Nothing Then _frmWaiter.SetCloseForm() End If End Sub End Class
源代码:FrmMsgWaitingFor (部分代码参考 水如烟的)
Public Class FrmMsgWaitingFor Protected Delegate Sub SetControlText(ByVal ctr As Control,ByVal s As String) Public Sub New(e As eWaitingFormType) InitializeComponent() Select Case e Case eWaitingFormType.Message HideWorkingImg() Me.ProgressBarMain.Visible = False Case (eWaitingFormType.Progress) HideWorkingImg() Me.LabelMessage.Visible = False Case eWaitingFormType.MessageAndProgress HideWorkingImg() Case eWaitingFormType.MessageAndWorking Me.ProgressBarMain.Visible = False End Select End Sub Private Sub HideWorkingImg() Me.PicWorking.Visible = False Me.PicWorking.Image = Nothing End Sub Protected Sub OnSetControlText(ByVal ctr As Control,ByVal s As String) If ctr.Invokerequired Then Dim m As New SetControlText(AddressOf OnSetControlText) Me.Invoke(m,New Object() {ctr,s}) Else ctr.Text = s End If Application.DoEvents() End Sub Public Sub SetFormText(ByVal s As String) OnSetControlText(Me,s) End Sub Public Sub SetMessage(ByVal s As String) OnSetControlText(Me.LabelMessage,s) End Sub Public Sub SetMessage(ByVal format As String,ByVal args() As Object) SetMessage(String.Format(format,args)) End Sub Public Sub SetCloseForm() If Me.Invokerequired Then Dim mi As New MethodInvoker(AddressOf SetCloseForm) Me.Invoke(mi) Else Me.Close() End If End Sub End Class
--执行界面:
可以收工了,不过还要以下问题没处理:
1.由于程序调用: Application.Run(_frmWaiter) 是在当前线程上运行标准应用程序消息循环,主界面会暂时失去焦点。应该有办法解决。不过可以同时允许多个等待提示窗体程序,等窗体关闭后,主界面重新得到焦点。
2.进度条没有处理Stype为:Blocks 百分比显示效果。
3.其他跨线程等问题。
4.如果有人想到:说到底还不如在后台执行工作中,发送信息给主线程更新UI不就得了,那么你有可能没明白我想表达的意思。