软件界面的响应特性是判断一款软件的非常重要的方面。一般来说,不管你软件功能做得有多么奇妙,如果软件有一点点死机的感觉都会让用户感到很讨厌,甚至怀疑你软件里是否藏有更大的问题。

要提高界面的响应特性,最好的办法莫过于使用多线程,并把呈现界面的线程独立出来。以前只有使用C++才能实现的多线程功能,现在在.Net框架下,所有的语言(包括VB)都可以使用了。不过,使用多线程比使用单一线程要麻烦得多,比如线程之间的同步问题,做得不好很容易出错,而有的时候这种错误要开发人员花上几个星期的时间才能找到。在Windows Form软件中使用多线程更是有一些限制。

下面我们就把在Windows Form软件中使用多线程要注意的问题给大家做一个介绍。

首 
先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该保持顺畅,当UI线程调用的API可能引起阻塞时间超过30毫秒时(比如访问CD-ROM等速度超慢的外设、进行远程调用等等)就应该考虑使用多线程。为什么是30毫秒?30毫秒的概念是人眼可以察觉到的一个迟滞,大约等同于电影里的一帧停留的时间,最长不要超过100毫秒。

第二,最方便和简单的多线程是使用线程池。通过线程池里的线程运行代码的最简便方法则是使用异步委托调用。注意委托调用通常是同步完成的,请使用BeginInvoke方法,这样就可以把要调用的方法排队到线程池里等候处理,而程序的流程会立刻返回到调用方(此处是UI线程),而调用方因此不会出现阻塞。

看看下面的例子我们就发现要使用线程池异步执行代码也并非十分复杂,这里我们利用System.Windows.Forms.MethodInvoker委托进行异步调用。注意MethodInvoker委托不接受方法参数,如果需要向异步执行的方法传递参数,请使用其他委托,或者需要自己定义。

private void StartSomeWorkFromUIThread () {
    // 我们要做的工作相对UI线程而言台慢了,用下面的方法异步进行处理
    MethodInvoker mi = new MethodInvoker(RunsOnWorkerThread);//这是入口方法
    mi.BeginInvoke(null, null); // 这样就不会阻塞
}

// 缓慢的工作在此方法内进行处理,使用线程池里的线程
private void RunsOnWorkerThread() {
    DoSomethingSlow();
}

归纳上述方法,对UI线程而言实际上就是:1、发出调用,2、立刻返回,具体运行过程不理了,这样UI线程就不会被阻塞。这种方法很重要,下面我们会深入介绍。除了上面的方法,还有其他使用线程池的方法,当然如果你高兴也可以自己创建线程。

第三,在Windows Form中使用多线程的,最重要的一条注意事项是,除了创建控件的线程以外,绝对不要在任何其他线程里面调用控件的成员(只有极个别情况例外),也就是说控件属于创建它的线程,不能从其他线程里面访问。这一条适用于所有从System.Windows.Forms.Control派生的控件(因此可以说是几乎所有控件),包括Form控件本身也是。举一反三,我们很容易得出这样的结论,控件的子控件必须由创建控件的线程来创建,比如一个表单上的按钮,比如由创建表单的线程来创建,因此,一个窗口中的所有控件实际上都活在同一个线程之中。在实际编程时,大多数的软件的做法都是让同一线程负责全部的控件,这就是我们所说的UI线程。看下面的例子:

// 这是由UI线程定义的Label控件
private Label lblStatus;
....
// 以下方法不在UI线程上执行
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    lblStatus.Text = "Finished!";    // 这是错的
}

我们要特别提醒大家,很多人刚开始的时候都会使用以上的方法来访问不在同一个线程里的控件(包括笔者本人),而且在1.0版.Net 框架上似乎没有发现问题,但是这根本就是错的,更糟糕的是,程序员在这里不会得到任何错误提示,一开始就上当受骗,之后会莫明其妙地发现其他错误,这就是Windows Form多线程编程的痛苦所在。笔者试过花很多时间来Debug自己写的Splash窗口突然消失的问题,结果还是失败了:笔者在软件的引导过程中,用另外一个线程里创建了一个Splash窗口来显示欢迎信息,然后尝试把主线程里引导的状态直接写入到Splash窗口上的控件中,开始还OK,可是过一会Splash窗口就莫明其妙消失了。

理解了这一点,我们应该留意到,有时候即使没有用System.Threading.Thread来显式创建一个线程,我们也可能因为使用了异步委托的BeginInvoke方法来隐式创建了线程(从线程池里),在这种线程里也同样不能调用UI线程所创建的控件的成员。

第四,由于上述限制,我们可能会感到很不方便,的确,当我们利用一个新创建的线程来执行某些花时间的运算时,怎样知道运算进度如何并通过UI反映给用户呢?解决方法很多!比如熟悉多线程编程的用户很快会想到,我们采用一些低级的同步方法,工作者线程把状态保存到一个同步对象中,让UI线程轮询(Polling)该对象并反馈给用户就可以了。不过,这还是挺麻烦的,实际上不用这样做,Control类(及其派生类)对象有一个Invoke方法很特别,这是少数几个不受线程限制的成员之一。我们前面说到,绝对不要在任何其他线程里面调用非本线程创建的控件的成员时,也说了“只有极个别情况例外”,这个Invoke方法就是极个别情况之一----Invoke方法可以从任何线程里面调用。下面我们来讲解Invoke方法。

Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程(即创建控件的线程)上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。注意,使用Invoke方法时,UI线程不能处于阻塞状态。以下MSDN里关于Invoke方法的说明:

“控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。
    委托可以是 EventHandler 的实例,在此情况下,发送方参数将包含此控件,而事件参数将包含 EventArgs.Empty。委托还可以是 MethodInvoker 的实例或采用 void 参数列表的其他任何委托。调用 EventHandler 或 MethodInvoker 委托比调用其他类型的委托速度更快。”

好了,说完Invoke,顺便说说BeginInvoke,毫无疑问这是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,能够使用异步方法时应尽量使用异步方法。

下面我们利用所学到的知识来改写上面那个简单的例子:

// 这是由UI线程定义的Label控件
private Label lblStatus;
....
// 以下方法不在UI线程上执行
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    // Do UI update on UI thread
    object[] pList = { this, System.EventArgs.Empty };
    lblStatus.BeginInvoke(
      new System.EventHandler(UpdateUI), pList);
}
// 切换回UI线程执行的入口
private void UpdateUI(object o, System.EventArgs e) {
    //现在没问题了,使用Invoke使得线程总是回到UI线程,所以我们可以放心大胆地调用控件的成员了
    lblStatus.Text = "Finished!";
}

第五,关于多线程编程还要考虑线程之间的同步问题、死锁和争用条件,有关这类问题的文章很多,我们就不赘述了。

C#用了多线程界面还是卡死相关推荐

  1. 新装 Win7 系统装完驱动精灵,一打开到检测界面就卡死——原因与解决方案

    新装 Win7 系统装完驱动精灵,一打开到检测界面就卡死--原因与解决方案 参考文章: (1)新装 Win7 系统装完驱动精灵,一打开到检测界面就卡死--原因与解决方案 (2)https://www. ...

  2. wxPython多线程界面卡死或在不同平台崩溃问题

    一.测试程序界面 二.不使用多线程代码如下(当点击开启日志的时候,直接导致界面卡死) #coding:utf-8 import sys import threading import time imp ...

  3. c# 多线程界面卡顿_C#多线程解决界面卡死问题的完美解决方案

    文章下最方有源码下载 问题描述: 当我们的界面需要在程序运行中不断更新数据时,当一个textbox的数据需要变化时. 为了让程序执行中不出现界面卡死的现像,最好的方法就是更新主界面,这样就不会出现卡死 ...

  4. c# 多线程界面卡顿_优化electron客户端卡顿的几种方案

    背景 公司需要做一个同步盘的客户端,框架技术选型方面使用了支持跨平台的Electron框架,其中一些核心功能就是文件的上传,和下载,考虑到node操作文件比较方便,起初把文件的下载上传操作放到主进程, ...

  5. c# 多线程界面卡顿_多线程同步等候 导致主界面UI卡顿,求解~

    多线程同步等待 导致主界面UI卡顿,求解~~~ 描述如下,有N个用户,我执行如下操作逻辑, FOR第一个循环,开 N个线程执行登陆操作,执行完毕后 (线程同步后),执行拨号操作,由于我使用线程同步,导 ...

  6. word关闭时卡死_英雄联盟最近再现重大Bug:游戏读取界面完全卡死

    如果你最近曾经在英雄联盟的登陆读取卡住过,那么恭喜了,其实你并不孤单.最近很多玩家都曾经报告过这个烦人读取卡死问题,甚至有些玩家因此输掉了游戏. 很多玩家说自己的游戏在读取游戏的时候完全死掉,而英雄联 ...

  7. Android多线程界面更新方法的总结

    Android多线程与界面交互的方法 Activity.runOnUiThread(Runnable) View.post(Runnable),View.postDelay(Runnable,long ...

  8. Centos图形化界面gnome-shell卡死的解决办法

    CentOS中因为内存等的关系其实图形化界面卡死是很经常的事 解决办法也很简单粗暴,大致分为以下三种: 第一种:直接打开终端输入startx就可以重新启动图形化界面 第二种:如果你第一种尝试失败的话可 ...

  9. CCID多线程界面-python

    具体功能: 读取一个带有SN号信息的.elsx表,可以根据CCID通讯的终端设备,匹配特定SN号段的相关信息,后根据与终端自定的协议发送.elsx表中设备SN号段中所有配置信息,方便多个设备终端录入配 ...

最新文章

  1. AndroidManifest.xml
  2. mysql 2003报错_为什么不建议在 MySQL 中使用 UTF-8?
  3. tensorflow计算图_简单谈谈Tensorflow的运行机制
  4. luogu1991 无线通讯网
  5. Vue 页面如何利用生命周期函数监听用户预览时长
  6. 【转载】Kali-linux安装之后的简单设置
  7. 校园导游图的课程设计(三)
  8. 洛谷 P2473 [SCOI2008]奖励关 解题报告
  9. Object-C 函数参数语法
  10. Android期末复习题库
  11. 不小心将 pip 卸载了,重新安装pip
  12. IT成长日记(一)——字体
  13. java编写自动化脚本生成apk_批处理脚本实现android应用程序自动化测试
  14. matlab的gca和gcf,MATLAB 中gcf、gca 以及gco 的区别
  15. matlab 条件方程组的解,solve 时解方程组的限制条件问题
  16. 微软正打造全新 Edge“Phoenix”浏览器?网友:画蛇添足?
  17. 哈萨克斯坦游记之一_过路老熊_新浪博客
  18. multisim变压器反馈式_Multisim在负反馈放大器性能测试中的应用
  19. 简单工厂—工厂方法—抽象工厂
  20. 『.NET Tools』C#/.NET 计算文本相似度

热门文章

  1. 贪心算法--多处最优服务次序问题
  2. python十五:函数作用域-函数即变量
  3. 在ASP.NET MVC 模型中 选择最好的方法将多个model(数据模型)传递到视图
  4. AMD加载器实现笔记(四)
  5. Swift 绘图板功能完善以及终极优化
  6. Brute Force STL --- UVA 146 ID Codes
  7. API 2.0Switching Basemaps
  8. datatable复制一行数据到本表
  9. C++ 统计字符串中某字符出现的次数
  10. c语言注释的开始标记符和结束标记符分别为,C语言程序设计填空题