在本文中,我们使用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#教程。
