学习一下 C#的多线程库的一些基本操作,了解多线程工作的适用场景以及某些情况下的解决方案
多线程的方法一般是由 System.Threading 命名空间来提供的
常用类可以参考
1. System.Threading.Thread 类
这是用于控制线程的基础类,可以通过这个类来操作线程。
以下是其中比较重要的属性:
ManagedThreadId
线程唯一标识符
ThreadState
检测线程状态
下面是一个线程的简单例子
1 | using System; |
上面说明了如何让线程后台运行的 threadone.IsBackground = true;
这个属性的作用就是使得主线程不必等后台线程执行完毕才结束,可以比后台线程提前结束。
还有直到线程调用结束才继续的 threadone.Join();
1 | using System; |
上面的代码演示了终止进程和继续进程的方法
Thread.CurrentThread.Abort(n);是抛出异常的语句,n 是错误参数。
同样我们也可以在主进程里面通过调用 threadone.Abort(5);来中断进程,
注意,这里必须包含一个参数,不然的话就会调用无参数的 catch,由于上面程序没有添加这个,所以不能继续进程,从而直接结束进程。
Thread.ResetAbort(); 这是继续执行进程的语句,没有这句的话整个进程就会结束,不会继续进行。
附加关于 catch 的用法
如果 try 发生了异常,则转入 catch 的执行。catch 有几种写法:
catch
这将捕获任何发生的异常。
catch(Exception e)
这将捕获任何发生的异常。另外,还提供 e 参数,你可以在处理异常时使用 e 参数来获得有关异常的信息。
catch(Exception 的派生类 e)
这将捕获派生类定义的异常,例如,我想捕获一个无效操作的异常,可以如下写:
catch(InvalidOperationException e)
{
….
}
这样,如果 try 语句块中抛出的异常是 InvalidOperationException,将转入该处执行,其他异常不处理。catch 可以有多个,也可以没有,每个 catch 可以处理一个特定的异常。.net 按照你 catch 的顺序查找异常处理块,如果找到,则进行处理,如果找不到,则向上一层次抛出。如果没有上一层次,则向用户抛出,此时,如果你在调试,程序将中断运行,如果是部署的程序,将会中止。
如果没有 catch 块,异常总是向上层(如果有)抛出,或者中断程序运行。
我们再来看一段代码
1 | using System; |
这段代码同时调用了 10 个进程,输出输出 0-9,但是结果却是意料之外的。
结果显示输出的数字都是混乱没有顺序可言的,这就是线程之间的不同步所导致。
所以现在我们需要一个方法,可以阻塞调用线程,同步访问进程
1 | using System; |
我们使用了 C#中 lock 的关键字,使得 lock 范围类的代码是处于一个安全的进程,不与其他进程所共享,而在 lock 范围之外的代码就又可以和其他进程同时执行导致混乱。
这里关注一下 lock 里面的 this,他是一个对象标记(必须是对象,不能是 int 等基本类型(会发生封装)或字符串(暂留)),会使得同一标记的代码不会同时执行。
其实如果你需要使得整个线程处于安全状态,那么你可能会调用 t.Join();等待进程执行完毕,不过这样为什么不用单进程而用多进程呢?这里需要注意的是 lock 的作用并不会作用与主进程,子进程依然和 main 在两条时间线上执行。
lock 的一些用法:
lock(objectA){codeB} 看似简单,实际上有三个意思,这对于适当地使用它至关重要:
- objectA 被 lock 了吗?没有则由我来 lock,否则一直等待,直至 objectA 被释放。
- lock 以后在执行 codeB 的期间其他线程不能调用 codeB,也不能使用 objectA。
- 执行完 codeB 之后释放 objectA,并且 codeB 可以被其他线程访问。
2017-3-12
除此之外,还有 System.Threading.Interlocked 类、Moniter 类、 Mutex 类、 ReaderWriterLock 类 是类似于 lock 类的一些类,但是又与 lock 有些不同,每一种类都有这自己的特性,至于用哪一个类就要看情况了。
如果想限制一个类只能同时被一个线程访问,那就可以通过添加属性并且继承System.ContextBoundObject 类来实现
1 | [System.Runtime.Remoting.Contexts.Synchronization] |
还有一种方法也是可以实现类似的功能 MethodImplAttribute
1 | using System; |
- 同步事件和等待句柄
1 | static AutoResetEvent autoEvent;//声明状态变量 |
使用以上方法有一个前提就是需要事件在同一 class 里面
- 关于多线程最大一个问题应该就是死锁了吧,避免死锁最好就是不要同时获取多个锁,如果一定要的话就需要用巧妙的方法咯
如果程序里面有多个线程需要执行,我们可以交给系统的线程池进行自动管理。线程池可以优化线程执行过程,提高数据的吞吐量。但是如果对线程有特殊的控制要求的话就不合适使用线程池。
线程池一个特点就是自动化,只需要把线程交给线程池,其他管理运行都不用管,但是这又丧失了一定的控制能力。
- 每个程序域里面只能有一个 threadpool
1 | using System; |
这里注意的是,线程池架构只允许给函数传递一个对象,如果需要传递多个值,就需要把值包装给一个类的对象作为参数传递给 QueueUserWorkItem 方法。