C#多线程实践——创建和开始使用
线程用Thread类来创建, 通过ThreadStart委托来指明方法从哪里开始运行。ThreadStart的声明如下:
public delegate void ThreadStart();
调用Start方法后,线程开始运行,直到它所调用的方法返回后结束。
![](https://common.cnblogs.com/images/copycode.gif)
class ThreadTest
{
static void Main()
{
Thread t = new Thread (new ThreadStart (Go));
t.Start();
Go();
}
static void Go() { Console.WriteLine ("hello!"); }
![](https://common.cnblogs.com/images/copycode.gif)
一个线程可以通过C#的委托简短的语法更便利地创建出来:
![](https://common.cnblogs.com/images/copycode.gif)
static void Main() {
Thread t = new Thread (Go); // 不需要显式声明使用 ThreadStart
t.Start();
...
}
static void Go() { ... }
在这种情况,ThreadStart被编译器自动推断出来:
![](https://common.cnblogs.com/images/copycode.gif)
另一个快捷的方式是使用匿名方法来启动线程
![](https://common.cnblogs.com/images/copycode.gif)
static void Main()
{
Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); });
t.Start();
}
![](https://common.cnblogs.com/images/copycode.gif)
线程有一个IsAlive属性,在调用Start()之后直到线程结束之前一直为true。一个线程一旦结束便不能重新开始了。
将数据传入ThreadStart中
假如想更好地区分开每个线程的输出结果,如让其中一个线程输出大写字母。可以考虑传入一个状态字到Go中来完成整个任务,此时就不能使用ThreadStart委托,因为它不接受参数。不过NET framework定义了另一个版本的委托叫ParameterizedThreadStart, 它可以接收一个单独的object类型参数,委托声明如下:
public delegate void ParameterizedThreadStart (object obj);
示例如下:
![](https://common.cnblogs.com/images/copycode.gif)
class ThreadDemo
{
static void Main()
{
Thread t = new Thread (Go); // 编译器自动推断
t.Start (true); // == Go (true)
Go (false);
}
static void Go (object upperCase)
{
bool upper = (bool) upperCase;
Console.WriteLine (upper ? "HELLO!" : "hello!");
}
![](https://common.cnblogs.com/images/copycode.gif)
在整个例子中,编译器自动推断出ParameterizedThreadStart委托,因为Go方法接收一个单独的object参数,就像这样写:
Thread t = new Thread (new ParameterizedThreadStart (Go));
t.Start (true);
ParameterizedThreadStart的特性是在使用之前我们必需对我们想要的类型(这里是bool)进行装箱操作,并且它只能接收一个参数。
一个替代方案是使用一个匿名方法调用一个普通的方法如下:
![](https://common.cnblogs.com/images/copycode.gif)
static void Main()
{
Thread t = new Thread (delegate() { WriteText ("Hello"); });
t.Start();
}
static void WriteText (string text) { Console.WriteLine (text); }
![](https://common.cnblogs.com/images/copycode.gif)
优点是目标方法(这里是WriteText)可以接收任意数量的参数,并且没有装箱操作。不过这需要将一个外部变量放入到匿名方法中,向下面的一样:
![](https://common.cnblogs.com/images/copycode.gif)
static void Main()
{
string text = "Before";
Thread t = new Thread (delegate() { WriteText (text); });
text = "After";
t.Start();
}
static void WriteText (string text) { Console.WriteLine (text); }
![](https://common.cnblogs.com/images/copycode.gif)
匿名方法出现了一种怪异的现象:当外部变量被后面的代码修改了值的时候,线程可能会通过外部变量进行无意的互动。换个角度看,有意的互动(通常通过字段)也可以采用这种方式!一旦线程开始运行了,外部变量最好被处理成只读的——除非有人愿意使用适当的锁。
另一种较常见的方式是将对象实例的方法而不是静态方法传入到线程中,对象实例的属性可以告诉线程要做什么,如下重写了上节的例子:
![](https://common.cnblogs.com/images/copycode.gif)
class ThreadDemo
{
bool upper; static void Main()
{
ThreadDemo instance1 = new ThreadDemo();
instance1.upper = true;
Thread t = new Thread (instance1.Go);
t.Start();
ThreadDemo instance2 = new ThreadDemo();
instance2.Go(); // 主线程——运行 upper=false
} void Go() { Console.WriteLine (upper ? "HELLO!" : "hello!"); }
![](https://common.cnblogs.com/images/copycode.gif)
命名线程
线程可以通过它的Name属性进行命名,这非常有利于调试:可以用Console.WriteLine打印出线程的名字,Microsoft Visual Studio可以将线程的名字显示在调试工具栏的位置上。线程的名字可以在被任何时间设置——但只能设置一次,重命名会引发异常。
程序的主线程也可以被命名,下面例子里主线程通过CurrentThread命名:
![](https://common.cnblogs.com/images/copycode.gif)
class ThreadNaming
{
static void Main()
{
Thread.CurrentThread.Name = "main";
Thread worker = new Thread (Go);
worker.Name = "worker";
worker.Start();
Go();
}
static void Go()
{
Console.WriteLine ("Hello from " + Thread.CurrentThread.Name);
}
}
![](https://common.cnblogs.com/images/copycode.gif)
前台和后台线程
线程默认为前台线程,这意味着任何前台线程在运行都会保持程序存活。C#也支持后台线程,当所有前台线程结束后,它们不维持程序的存活。
改变线程从前台到后台不会以任何方式改变它在CPU协调程序中的优先级和状态。
线程的IsBackground属性控制它的前后台状态,如下实例:
![](https://common.cnblogs.com/images/copycode.gif)
class PriorityTest
{
static void Main (string[] args)
{
Thread worker = new Thread (delegate() { Console.ReadLine(); });
if (args.Length > 0) worker.IsBackground = true;
worker.Start();
}
}
![](https://common.cnblogs.com/images/copycode.gif)
如果程序被调用的时候没有任何参数,工作线程为前台线程,并且将等待ReadLine语句来等待用户的触发回车,这期间,主线程退出,但是程序保持运行,因为一个前台线程仍然活着。 另一方面如果有参数传入Main(),工作线程被赋值为后台线程,当主线程结束程序立刻退出,终止了ReadLine。后台线程这种终止方式,使任何最后操作都被规避了,这是不太合适的。好的方式是明确等待任何后台工作线程完成后再结束程序,可能用一个timeout(大多用Thread.Join)。如果因为某种原因某个工作线程无法完成,可以试图终止它,如果失败了,再抛弃线程,允许它与进程一起消亡。(记录是一个难题,但在这个场景下是有意义的)
拥有一个后台工作线程是有益的,最直接的理由是结束程序时它可能有最后的发言权,与不会消亡的前台线程一起保证程序的正常退出。抛弃一个前台工作线程风险更大,尤其对Windows Forms程序,因为程序直到主线程结束时才退出(至少对用户来说),但是它的进程仍然运行着。它将从应用程序栏消失不见,但却可以在在Windows任务管理器进程栏找到它。除非手动找到并结束它,否则将继续消耗资源,并可能阻止一个新的实例的重新开始运行或影响它的特性。
对于程序失败退出的普遍原因就是存在“被忘记”的前台线程。
线程优先级
线程的Priority 属性确定了线程相对于其它同一进程的活动的线程拥有多少执行时间,以下是级别:
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
只有多个线程同时为活动时,优先级才有作用。
设置一个线程的优先级为高一些,并不意味着它能执行实时的工作,因为它受限于程序的进程级别。要执行实时的工作,必须提升在System.Diagnostics 命名空间下Process的级别,像下面这样:
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
ProcessPriorityClass.High 其实是一个时间片中的最高优先级别:Realtime。设置进程级别到Realtime通知操作系统:你不想让你的进程被抢占了。如果你的程序进入一个偶然的死循环,可以预期,操作系统被锁住了,除了关机没有什么可以拯救你了!基于此,High大体上被认为最高的有用进程级别。
如果一个实时的程序有一个用户界面,提升进程的级别是不太好的,因为当用户界面UI过于复杂的时候,界面的更新耗费过多的CPU时间,拖慢了整台电脑。 降低主线程的级别、提升进程的级别、确保实时线程不进行界面刷新,但这样并不能避免电脑越来越慢,因为操作系统仍会拨出过多的CPU给整个进程。最理想的方案是使实时工作和用户界面在不同的进程(拥有不同的优先级)运行,通过Remoting或共享内存方式进行通信,共享内存需要Win32 API中的 P/Invoking。(可以搜索看看CreateFileMapping 和 MapViewOfFile)
异常处理
任何线程创建范围内try/catch/finally块,当线程开始执行便不再与其有任何关系。考虑下面的程序:
![](https://common.cnblogs.com/images/copycode.gif)
public static void Main()
{
try
{
new Thread (Go).Start();
}
catch (Exception ex)
{
// 不会在这得到异常
Console.WriteLine ("Exception!");
} static void Go() { throw null; }
}
![](https://common.cnblogs.com/images/copycode.gif)
这里
try
/
catch
语句一点用也没有,新创建的线程将引发NullReferenceException异常。当你考虑到每个线程有独立的执行路径的时候,便知道这行为是有道理的。补救方法是在线程处理的方法内加入他们自己的异常处理。
![](https://common.cnblogs.com/images/copycode.gif)
public static void Main()
{
new Thread (Go).Start();
} static void Go()
{
try {
...
throw null; // 这个异常在下面会被捕捉到
...
}
catch (Exception ex)
{
记录异常日志,并且或通知另一个线程
我们发生错误
...
}
![](https://common.cnblogs.com/images/copycode.gif)
从.NET 2.0开始,任何线程内的未处理的异常都将导致整个程序关闭,这意味着忽略异常不再是一个选项了。因此为了避免由未处理异常引起的程序崩溃,try/catch块需要出现在每个线程进入的方法内,至少要在产品程序中应该如此。对于经常使用“全局”异常处理的Windows Forms程序员来说,这可能有点麻烦,像下面这样:
![](https://common.cnblogs.com/images/copycode.gif)
using System;
using System.Threading;
using System.Windows.Forms; static class Program
{
static void Main()
{
Application.ThreadException += HandleError;
Application.Run (new MainForm());
} static void HandleError (object sender, ThreadExceptionEventArgs e)
{
记录异常或者退出程序或者继续运行...
}
}
![](https://common.cnblogs.com/images/copycode.gif)
Application.ThreadException事件在异常被抛出时触发,以一个Windows信息(比如:键盘,鼠标活着 "paint" 等信息)的方式,简言之,一个Windows Forms程序的几乎所有代码。虽然这看起来很完美,它使人产生一种虚假的安全感——所有的异常都被中央异常处理捕捉到了。由工作线程抛出的异常便是一个没有被Application.ThreadException捕捉到的很好的例外。(在Main方法中的代码,包括构造器的形式,在Windows信息开始前先执行)
.NET framework为全局异常处理提供了一个更低级别的事件:AppDomain.UnhandledException,这个事件在任何类型的程序(有或没有用户界面)的任何线程有任何未处理的异常触发。尽管它提供了好的不得已的异常处理解决机制,但是这不意味着这能保证程序不崩溃,也不意味着能取消.NET异常对话框。
在产品程序中,明确地使用异常处理在所有线程进入的方法中是必要的,可以使用包装类和帮助类来分解工作来完成任务,比如使用BackgroundWorker类。
C#多线程实践——创建和开始使用的更多相关文章
- 多线程实践—Python多线程编程
多线程实践 前面的一些文章和脚本都是只能做学习多线程的原理使用,实际上什么有用的事情也没有做.接下来进行多线程的实践,看一看在实际项目中是怎么使用多线程的. 图书排名示例 Bookrank.py: 该 ...
- iOS开发多线程篇—创建线程
iOS开发多线程篇—创建线程 一.创建和启动线程简单说明 一个NSThread对象就代表一条线程 创建.启动线程 (1) NSThread *thread = [[NSThread alloc] in ...
- css3实践—创建3D立方体
css3实践-创建3D立方体 要想实现3D的效果,其实非常简单,只需指定一个元素为容器并设置transform-style:preserve-3d,那么它的后代元素便会有3D效果.不过有很多需要注意的 ...
- Win32 多线程的创建方法和基本使用
Win32多线程的创建方法主要有: (1)CreateThread() (2)_beginthread()&&_beginthreadex() (3)AfxBeginThread() ...
- Python多线程的创建,相关函数和守护线程的理解
一:多线程的创建 threading库创建线程有两种方式,函数式和继承式 1)函数式 def func(): print 'Starting' print 'Ending' t=threadin ...
- Java多线程(1) 创建
一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下以下这张较为经典的图: Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Threa ...
- 牛客网Java刷题知识点之什么是进程、什么是线程、什么是多线程、多线程的好处和弊端、多线程的创建方式、JVM中的多线程解析、多线程运行图解
不多说,直接上干货! 什么是进程? 正在进行中的程序(直译). 什么是线程? 就是进程中一个负责程序执行的控制单元(执行路径). 见 牛客网Java刷题知识点之进程和线程的区别 什么是多线程? 一个进 ...
- java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)
今天开始就来总结一下Java多线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1): 1.什么是线程以及多线程与进程的区别 2.多线程的创建与启动 3.中断线程和守护线程以及 ...
- B1. Concurrent 多线程的创建
[概述] 多线程的创建常用的有两种方法:1). 继承 Thread 类: 2). 实现 Runnable 接口: 3). 实现 Callable 接口. [继承 Thread 类] /** * 1. ...
随机推荐
- 新写PHP HTTP断点续传类文件代码
一个支持断点续传的PHP文件下载类文件,调用方法简单,类代码简洁,可记忆上次的下载的节点,实现累积下载,类名称download,类代码如下: function download($path,$file ...
- ssh登陆设置快捷方式
在自己的环境下配置 ~/.ssh/config Host k231 HostName 192.168.1.231 User kyee 原来ssh 登陆192.168.1.231 的命令是 ssh ky ...
- 深入浅出Java 重定向和请求转发的区别
深入浅出Java 重定向和请求转发的区别 <span style="font-family:FangSong_GB2312;font-size:18px;">impor ...
- python中列表和字典的高级应用
1.将序列分解为单独的变量 1.1问题 包含n个元素的元组或列表.字符串.文件.迭代器.生成器,将它分解为n个变量 1.2方案 直接通过赋值操作 要求:变量个数要等于元素个数 当执行分解操作时,有时需 ...
- C语言学习笔记--指针与字符串
字符类型 char(character)是一种整数,也是一种特殊的类型:字符.这是因为 ① 用单引号表示的字符字符字面量:‘a’,'1' ②‘’也是一个字符 ③printf和scanf里用%c来输入. ...
- Spark Streaming 数据接收过程
SparkStreaming 源码分析 一节中从源码角度,描述了Streaming执行时代码的调用过程.下边就接收转化阶段过程再简单分析一下,为分析backpressure作准备. SparkStre ...
- 转:用 git 下载 uboot 源码
1. 起因: 想下载 uboot 源码,原先的方法都是下载压缩包,然后放到虚拟机上的 Ubuntu ,再解压. 在看 uboot 源码的时候,发现 v2016.01 版本的uboot中关于 board ...
- PHP 访问类中的静态属性
静态属性和普通属性不一样,静态属性只属于类本身而不属于类的任何实例,所以他们的访问方式也不一样.你可以把静态属性认为是存储在类当中的全局变量,而且你可以在任何地方通过类来访问它们. 在类本身中访问静态 ...
- 装饰者模式 (decorator pattern)
参考 : Head First 设计模式(中文版) 这篇只作为个人温习! 用意 : 动态地给一个对象添加|扩展一些行为.Decorator 强调用对象组合而非继承来实现扩展,这显得较为灵活. 角色: ...
- python常用数据结构的常用操作
作为基础练习吧.列表LIST,元组TUPLE,集合SET,字符串STRING等等,显示,增删,合并... #===========List===================== shoplist ...