在CSharp中,使用Thread类进行创建和管理线程,通过这种方式创建的线程受CLR管理,由GC来分配和回收内存,因此也叫托管线程。
关于线程和进程:一个应用程序有至少一个进程;一个进程至少有一个线程。
进程与线程
进程。程序在运行时所占用的计算资源称之为进程。进程之间相互独立,所以进程之间通信会比较困难。
线程。线程是程序执行的最小单位。线程也包含自己的计算资源。在一个进程中可以有一个到多个线程。
多线程。指一个进程中多个线程并发执行。
同步与异步的区别
同步是只在一个线程中(主线程)从上向下按照顺序执行,每次只有执行完毕才会继续往下执行。
异步指在主线程运行过程中,开一个新的线程(子线程)去执行耗时的操作,主线程不会等待子线程的计算结果直接继续执行下去。
CSharp中的多线程
在C#中 Thread
类是.Net CLR对线程对象的抽象封装。多线程是操作系统提供的,Thread向操作系统申请线程。
异步多线程的特点
- 不会卡死主线程,更好的用户体验。
- 多个线程比单个线程处理速度快。系统资源换性能,非线性减少处理时间。
- 多个线程开始、结束 的先后顺序不固定。
BeginInvoke
method.Invoke() 主线程同步执行method方法。使用BeginInvoke()可以在子线程中异步调用方法,BeginInvoke()包含两个参数:返回一个IAsyncResult的结果。下面是如何处理异步的回调
1 | int MainThreadID = Thread.CurrentThreadManagedThreadId; |
获取进程信息
1 | Thread thread = new Thread(() => |
创建线程
ThreadStart
ThreadStart 是一个无参委托。使用时无需传递参数,可以通过类的成员变量、静态变量传递信息。但是多线程时要进行加锁处理。
1 | // public Thread(ThreadStart start) |
ParameterizedThreadStart
ParameterizedThreadStart 是一个带有一个object类型的委托。可以向线程内传递参数,但是object类型需要装箱拆箱。
1 | static void Main(string[] args) |
Lambda
像上面 获取进程信息
中那样直接传递一个委托方便快捷。
线程状态与切换
1 | public enum ThreadState |
线程的休眠与阻塞
使用 Thread.Sleep()
、Thread.Yield()
可以将线程挂起一段时间。Thread.Join()
方法可以 阻塞当前进程一直等到另一个线程运行结束。
在 Join 或 Sleep 过程中,线程是阻塞的。
阻塞的定义
当线程由于特定原因暂停执行,那么它就是阻塞的。如果线程处于阻塞状态,线程就会交出他的 CPU 时间片,并且不会消耗 CPU 时间,直至阻塞结束,即线程会阻塞,但是 CPU 会继续找到其他未阻塞的线程继续执行指令,避免 CPU 空闲。阻塞会发生 CPU 从一个线程换到另一个线程执行,即上下文切换。
- Thread.Sleep
- 线程会放弃自己的CPU时间,将CPU交给其他线程执行。即使没有其他就绪的线程也不会占用CPU时间。
- 可以调度任何处理器的线程使用时间片。
- Thread.Yield
- 线程会让出自己的CPU时间,但是当没有其他就绪的线程时,它会继续执行。
- 只能调度运行在当前CPU的线程。
- Thread.Join
用来阻塞线程,阻塞的线程只有执行完毕后才会继续执行其他线程(包括主线程)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24static void Main()
{
Console.WriteLine("AAA");
var thread = new Thread(Test);
thread.Start();
thread.Join();
Console.WriteLine("BBB");
}
public static void Test()
{
for (int i = 0; i < 10; i++)
{
Console.Write(i);
Thread.Sleep(100);
}
}
//> AAA
//> 0123456789BBB
// 如果注释掉 thread.Join() 则运行结果为:
//> AAA
//> BBB
//> 0123456789
线程的终止与恢复
- 通过
Abort()
方法来强制停止正在执行的线程。他会抛出一个ThreadAbortException
的异常从而终止线程。 - 在抛出
ThreadAbortException
异常中,线程还未结束,可以通过ResetAbort()
方法从而恢复终止的线程。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static void print()
{
FLAG:
try{
for (int i = 0; i < 20; i++)
{
Console.Write(i); Thread.Sleep(100);
}
}
catch (ThreadAbortException e)
{
Console.WriteLine("子线程要终止了");
Thread.ResetAbort();
Console.WriteLine("子线程又恢复了");
goto FLAG;
}
Console.WriteLine("子线程执行完毕");
}
static void Main(string[] args)
{
Thread thread = new Thread(print);
thread.Start();
Task.Delay(300).Wait();
thread.Abort();
}
//> 0123子线程要终止了
//> 子线程又恢复了
//> 012345678910111213141516171819子线程执行完毕
线程的优先级
1 | public enum ThreadPriority |
可以通过 Thread.IsBackground 方法可以设置是否为后台线程。前台线程的优先级是高于后台线程的。
线程优先级过大可能会占用大量CPU时间导致系统无响应或卡顿。线程优先级过低会导致过少CPU时间占用导致窗口无相应。
优先级越高分配的CPU时间越多,优先级越低分配CPU时间越少。
处理线程的异常
只能在线程内部try/catch异常。