在本文中,我们使用C#中的线程。
进程是计算机中运行的程序实例。当进程启动时,公共语言运行库(CLR)会自动创建一个单一的前台线程来执行应用程序代码。一个进程可以运行多个线程。一个线程是一个程序唯一的执行路径。
在C#中,Thread
类表示一个线程。它创建并控制线程、设置其优先级并获取其状态。Thread
类是System.Threading
命名空间的一部分。
进程和线程都是独立的执行序列。下表总结了进程和线程的区别:
Process | Thread |
---|---|
进程在单独的内存中运行(进程隔离) | 线程共享内存 |
使用更多内存 | 使用更少内存 |
不可能变成僵尸 | |
更多开销 | 更少开销 | 创建和销毁速度较慢 | 创建和销毁速度较快 |
更易于编码和调试 | 可以成为更难编码和调试 |
C#线程启动
Start
方法启动一个线程。
Console.WriteLine("main started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}"); var t = new Thread(task); t.Start(); Console.WriteLine("main finished"); void task() { Console.WriteLine("thread started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id}"); }
创建一个新线程,然后使用Start
启动。
Console.WriteLine("main started");
主程序本身就是一个单独的执行线程。
var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}");
Thread.CurrentThread.ManagedThreadId
获取当前托管线程的唯一标识符。
var t = new Thread(task);
创建了一个新的线程
。我们传递对在线程中执行的函数的引用。
t.Start();
线程启动。
void task() { Console.WriteLine("thread started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id}"); }
在函数内部,我们获取并打印线程ID。
$ dotnet run main started main id: 1 main finished thread started thread id: 4
注意主线程在其他线程之前完成。程序等待线程完成。
C#线程传递参数
在下一个示例中,我们将展示如何将参数传递给线程。
int n = 5; string word = "falcon"; // var t = new Thread(() => { for (int i = 0; i < n; i++) Console.WriteLine(word); }); var t = new Thread(() => repeat(n, word)); t.Start(); void repeat(int n, string word) { for (int i = 0; i < n; i++) { Console.WriteLine(word); } }
线程打印一个单词n次。我们将单词和数字作为参数传递。
var t = new Thread(() => repeat(n, word));
线程采用lambda表达式,其中repeat函数使用两个参数调用。
$ dotnet run falcon falcon falcon falcon falcon
C#线程.睡眠
Tread.Sleep
方法将当前线程挂起指定的毫秒数。该方法对调试和测试很有用。
常用于模拟长时间运行的任务。
for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); var t = new Thread(() => task(n)); t.Start(); } void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); }
在程序中,我们创建了5个线程,它们休眠的时间是随机的毫秒数。
var n = new Random().Next(500, 1500);
我们创建一个介于500和1500之间的随机数。
var t = new Thread(() => task(n)); t.Start();
我们将随机数传递给新创建的线程。
void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); }
在线程内运行的函数使用Tread.Sleep
暂停执行n毫秒。
$ dotnet run thread id: 5 started thread id: 6 started thread id: 4 started thread id: 7 started thread id: 8 started thread id: 5 finished in 822 ms thread id: 8 finished in 891 ms thread id: 6 finished in 902 ms thread id: 4 finished in 946 ms thread id: 7 finished in 1113 ms
C#前台&后台线程
有两个线程之王:前台和后台。后台线程不会阻止进程终止。当属于一个进程的所有前台线程都终止时,CLR将结束该进程。
默认线程是前台线程。我们使用IsBackground
属性将线程更改为后台线程。
Console.WriteLine("started main"); for (var i = 0; i < 5; i++) { var rn = new Random().Next(500, 1500); var t = new Thread(() => task(rn)); t.IsBackground = true; t.Start(); } void task(int n) { Thread.Sleep(n); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"{id} finished in {n} ms"); } Console.WriteLine("finished main");
在示例中,我们创建了一个主程序线程和五个后台线程。
$ dotnet run started main finished main
一旦唯一的前台线程完成,所有其他后台线程都终止,程序结束。后台线程没有时间运行。
$ dotnet run started main finished main 8 finished in 572 ms 4 finished in 770 ms 7 finished in 772 ms 6 finished in 1145 ms 5 finished in 1397 ms
当我们调用t.IsBackground=true;
行时,我们创建了前台线程。然后主线程等待其他前台线程完成并运行五个线程。
C#线程连接
Join
方法会阻塞调用线程,直到指定的线程终止。
Console.WriteLine("main started"); var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"main id: {id}"); Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); threads[i] = new Thread(() => task(n)); } foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } void task(int n) { var id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine($"thread id: {id} started"); Thread.Sleep(n); Console.WriteLine($"thread id: {id} finished in {n} ms"); } Console.WriteLine("main finished");
在这个程序中,主线程等待所有其他线程完成。
Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { var n = new Random().Next(500, 1500); threads[i] = new Thread(() => task(n)); }
我们创建了一个包含五个线程的数组,这些线程将休眠随机数毫秒。
foreach (var thread in threads) { thread.Start(); }
首先,我们启动所有五个线程。
foreach (var thread in threads) { thread.Join(); }
使用Join
,我们会阻塞主线程,直到数组中的所有五个线程都完成。
$ dotnet run main started main id: 1 thread id: 4 started thread id: 5 started thread id: 6 started thread id: 7 started thread id: 8 started thread id: 7 finished in 802 ms thread id: 4 finished in 1080 ms thread id: 8 finished in 1354 ms thread id: 6 finished in 1358 ms thread id: 5 finished in 1461 ms main finished
带秒表的C#线程
使用秒表
,我们可以准确地测量经过的时间。
using System.Diagnostics; Console.WriteLine(Thread.CurrentThread.ManagedThreadId); var sw = new Stopwatch(); sw.Start(); Thread[] threads = new Thread[10]; for (var i = 0; i < 10; i++) { var t = new Thread(() => { var id = Thread.CurrentThread.ManagedThreadId; var r = new Random().Next(500, 1500); Thread.Sleep(r); Console.WriteLine($"{id} finished in {r} ms"); } ); threads[i] = t; } foreach (var t in threads) { t.Start(); } foreach (var t in threads) { t.Join(); } sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms");
我们创建了十个运行随机毫秒数的线程。主线程等待所有其他线程完成并计算经过的时间。
var sw = new Stopwatch(); sw.Start();
我们创建了Stopwatch
并运行它。
sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms");
最后,我们计算经过的时间并打印结果。
$ dotnet run 1 13 finished in 539 ms 4 finished in 547 ms 9 finished in 617 ms 6 finished in 782 ms 8 finished in 787 ms 7 finished in 917 ms 10 finished in 968 ms 12 finished in 1170 ms 5 finished in 1468 ms 11 finished in 1488 ms elapsed: 1488 ms
程序运行的时间与最长的线程一样长。
在本文中,我们使用了C#中的线程。
列出所有C#教程。