Win Form程序线程点点

时间:2021-07-02 01:01:37

消息循环

Win32窗体程序基于消息驱动的,程序的模型就是一个用户触发事件消息->系统分发事件消息->程序处理事件的循环过程.

.NET Win Form程序对消息循环进行了封装,可以看到Application.Run方法其实就是在当前UI线程启动一个消息循环.
Win Form程序线程点点

工作线程

每个Win Form进程默认会开启两个线程:

  • 一个是主线程,即我们熟知的UI线程,所有的程序处理默认都在此线程上运行.
  • 另外一个线程是用于监听处理系统级事件的,如系统注销等,详细的系统事件列表可见SystemEvents类的说明.
    在单独线程上处理系统级事件,就不会造成程序阻塞.

Win Form程序线程点点

下面通过在系统事件处理过程中增加断点,确定系统事件处理是在单独的名字为.NET SystemEvents线程上运行的.

Win Form程序线程点点

强制重绘

既然系统默认只有一个线程做应用程序的处理.如果在一个长时间的处理过程中,又发生了其他事件,譬如界面无效需要重绘事件,或者就是一个用户点击了一个按钮,由于是单线程,这些后续事件只能等待处理,造成了界面不更新或者就是假死的现象.

下面的代码通过在特定的事件内循环更新界面上的label文本来模拟模拟一个长时间的操作.

Win Form程序线程点点

可以看到,界面上的label文本在最后才会更新,并没有按照预期更新.

Win Form程序线程点点

如果想实时更新,就需要通过执行Refresh方法去重绘界面.

Win Form程序线程点点

Win Form程序线程点点

过程中处理其他事件

在处理当前事件的过程中,如果需要响应其它事件消息,可以通过Application.DoEvents()方法来实现.
Win Form程序线程点点
譬如前面的例子中,如果用户点击了退出按钮,程序需要马上退出.在没有在过程中加入DoEvents方法的情况下,程序只会等待直到整个长时间执行过程完成后才会退出.如果马上响应退出,就在过程中调用DoEvents方法来实时处理消息.

Win Form程序线程点点

效果:

Win Form程序线程点点

前面的方法合理吗?

Application.DoEvents()方法中断了当前的操作,而跳去了处理其他事件.如果在其他事件处理代码中,可能又会触发其他事件,这样会出现不可预料的结果,或者会引致死循环(后面的事件又触发了最初的事件).

对于耗时任务,我们自然想到多线程处理.
so创建一个工作线程,并把那个长时间的任务分配到此新线程上执行.

Win Form程序线程点点

运行代码,发现异常了.

Win Form程序线程点点

原来,为保证线程安全,.NET控件不允许从非创建它的线程*问它.否则,不同线程访问控件,很容易会造成问题.
譬如数据脏读.或者你说加锁访问吧,但这会引致另外一个问题:死锁.
要处理这个问题,可以有以下方法:

  1. 蒙着眼睛,设置Control.CheckForIllegalCrossThreadCalls=False跳过检测.
  2. 乖乖遵守规定,各司其职,在UI线程上去访问控件,方法很简单,利用Control.Invoke()方法.
    Win Form程序线程点点
    效果,一切正常:
    Win Form程序线程点点
  3. 上面第二个方法需要处理低级的线程操作问题,需要手工创建线程,退出(Abort)线程.其实.NET已经提供了BackgroundWorker这个类封装了后台线程的相关操作,而且是基于事件模型的,很容易使用.下面用BackGroundWorker这个类改写一个程序.
      a.创建对象并初始化
    Win Form程序线程点点
    b.事件处理代码.
    Win Form程序线程点点
    Win Form程序线程点点

Win Form程序线程点点

Win Form程序线程点点
              效果:
              Win Form程序线程点点

参考

How to: Make Thread-Safe Calls to Windows Forms Controls

Threading in a windows form