进程和线程(线程是轻量级进程)(中)
创建线程
线程是通过扩展Thread类创建的。扩展的 Thread 类调用Start()方法来开始子线程的执行。
using System;
using System.Threading;
namespace MulityThreadNote
{
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(PrintNumbers));//无参数的委托
t1.Start();
Thread t2 = new Thread(new ParameterizedThreadStart(PrintNumbers));//有参数的委托
t2.Start(10);
Console.ReadLine();
}
static void PrintNumbers()
{
Console.WriteLine("无参数委托Starting...");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
}
//注意:要使用ParameterizedThreadStart,定义的参数必须为object
//如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。
static void PrintNumbers(object count)
{
Console.WriteLine("有参数委托Starting...");
for (int i = 0; i < Convert.ToInt32(count); i++)
{
Console.WriteLine(i);
}
}
}
}
注释:我们只需指定在不同线程运行的方法名,而C#编译器会在后台创建这些对象
如果为了简单,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值
static void Main(string[] args)
{
//通过匿名委托创建
Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通过匿名委托创建的线程"); });
thread1.Start();
//通过Lambda表达式创建
Thread thread2 = new Thread(() => Console.WriteLine("我是通过Lambda表达式创建的委托"));
thread2.Start();
Console.ReadKey();
}
运行结果:
管理线程
Thread类提供了各种管理线程的方法。
下面的实例演示了Sleep()方法的使用,用于在一个特定的时间暂停线程。
using System;
using System.Threading;
namespace MultithreadingApplication
{
class ThreadCreationProgram
{
public static void CallToChildThread()
{
Console.WriteLine("Child thread starts");
// 线程暂停 5000 毫秒
int sleepfor = 5000;
Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
Thread.Sleep(sleepfor);
Console.WriteLine("Child thread resumes");
}
static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
Console.ReadKey();
}
}
}
注释:也可使用Thread.Sleep(TimeSpan.FromSeconds(5)); 暂停线程
运行结果:等待5秒后出现 Child thread resumes
销毁线程
Abort()方法用于销毁线程。
通过抛出ThreadAbortException在运行时中止线程。这个异常不能被捕获,如果有finally块,控制会被送至finally块。
下面的程序说明了这点:
using System;
using System.Threading;
namespace MultithreadingApplication
{
class ThreadCreationProgram
{
public static void CallToChildThread()
{
try
{
Console.WriteLine("Child thread starts");
// 计数到 10
for (int counter = 0; counter <= 10; counter++)
{
Thread.Sleep(500);
Console.WriteLine(counter);
}
Console.WriteLine("Child Thread Completed");
}
catch (ThreadAbortException e)
{
Console.WriteLine("Thread Abort Exception");
}
finally
{
Console.WriteLine("Couldn't catch the Thread Exception");
}
}
static void Main(string[] args)
{
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
// 停止主线程一段时间
Thread.Sleep(2000);
// 现在中止子线程
Console.WriteLine("In Main: Aborting the Child thread");
childThread.Abort();
Console.ReadKey();
}
}
}
运行结果:
线程等待
using System;
using System.Threading;
namespace MulityThreadNote
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main:Starting...");
Thread t = new Thread(PrintNumbersWithDelay);
t.Start();
t.Join(); //使用Join等待t完成
PrintNumbers();
Console.WriteLine("THread Complete");
Console.ReadLine();
}
static void PrintNumbers()
{
Console.WriteLine("PrintNumbers:Starting...");
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}
}
static void PrintNumbersWithDelay()
{
Console.WriteLine("PrintNumbersWithDelay:Starting...");
for (int i = 0; i < 5; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine(i);
}
}
}
}
注释:使用t.Join(); 等待t完成
运行结果:
检测线程状态
using System;
using System.Threading;
namespace DelegateDemo
{
class Program
{
private static void PrintNumbersWithStatus()
{
Console.WriteLine("PrintNumbersWithStatus:Starting...");
Console.WriteLine("PrintNumbersWithStatus:" + Thread.CurrentThread.ThreadState.ToString());//获取当前线程状态
for (int i = 0; i < 5; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine("PrintNumbersWithStatus:" + i);
}
}
private static void DoNothing()
{
Thread.Sleep(TimeSpan.FromSeconds(2));
}
static void Main(string[] args)
{
Console.WriteLine("Main:Start Program...");
Thread t1 = new Thread(PrintNumbersWithStatus);
Thread t2 = new Thread(DoNothing);
Console.WriteLine("Main1:" + t1.ThreadState.ToString());//获取实例线程状态
t2.Start();
t1.Start();
for (int i = 0; i < 30; i++)
{
Console.WriteLine("Main2:" + t1.ThreadState.ToString());
}
Thread.Sleep(TimeSpan.FromSeconds(5));
t1.Abort();
Console.WriteLine("thread t1 has been aborted");
Console.WriteLine("Main3:" + t1.ThreadState.ToString());
Console.WriteLine("Main4:" + t2.ThreadState.ToString());
Console.ReadKey();
}
}
}
注释:使用Thread.ThreadState获取线程的运行状态。ThreadState是一个C#枚举。谨记:不要在程序中使用线程终止,否则可能会出现意想不到的结果
运行结果:
前台线程和后台线程
前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程
后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。
通过BeginXXX方法运行的线程都是后台线程。
using System;
using System.Diagnostics;
using System.Threading;
namespace MulityThreadNote
{
class Program
{
static void Main(string[] args)
{
//演示前台、后台线程
BackGroundTest background = new BackGroundTest(10);
//创建前台线程
Thread fThread = new Thread(new ThreadStart(background.RunLoop));
//给线程命名
fThread.Name = "前台线程";
BackGroundTest background1 = new BackGroundTest(20);
//创建后台线程
Thread bThread = new Thread(new ThreadStart(background1.RunLoop));
bThread.Name = "后台线程";
//设置为后台线程
bThread.IsBackground = true;
//启动线程
fThread.Start();
bThread.Start();
}
}
class BackGroundTest
{
private int Count;
public BackGroundTest(int count)
{
this.Count = count;
}
public void RunLoop()
{
//获取当前线程的名称
string threadName = Thread.CurrentThread.Name;
for (int i = 0; i < Count; i++)
{
Console.WriteLine("{0}计数:{1}",threadName,i.ToString());
//线程休眠500毫秒
Thread.Sleep(1000);
}
Console.WriteLine("{0}完成计数",threadName);
}
}
}
运行结果:前台线程执行完,后台线程未执行完,程序自动结束。
把bThread.IsBackground = true注释掉,运行结果:主线程执行完毕后(Main函数),程序并未结束,而是要等所有的前台线程结束以后才会结束。
后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。
向线程传递参数
using System;
using System.Diagnostics;
using System.Threading;
namespace MulityThreadNote
{
class Program
{
static void Main(string[] args)
{
ThreadSample sample = new ThreadSample(5);
Thread t1 = new Thread(sample.CountNumbers);
t1.Name = "ThreadOne";
t1.Start();
t1.Join();
Console.WriteLine("--------------------------");
Thread t2 = new Thread(Count);
t2.Name = "ThreadTwo";
t2.Start(3);
t2.Join();
Console.WriteLine("--------------------------");
//使用lambda表达式引用另一个C#对方的方式被称为闭包。当在lambda表达式中使用任何局部变量时,C#会生成一个类,并将该变量作为该类的一个属性,但是我们无须定义该类,C#编译器会自动帮我们实现
Thread t3 = new Thread(()=> CountNumbers(5));
t3.Name = "ThreadThree";
t3.Start();
t3.Join();
Console.WriteLine("--------------------------");
int i = 10;
Thread t4 = new Thread(() => PrintNumber(i));
i = 20;
Thread t5 = new Thread(() => PrintNumber(i));
t4.Start();
t5.Start();
//t4, t5都会输出20, 因为t4,t5没有Start之前i已经变成20了
Console.ReadKey();
}
static void Count(object iterations)
{
CountNumbers((int)iterations);
}
static void CountNumbers(int iterations)
{
for (int i = 1; i <= iterations; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(0.5));
Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
}
}
static void PrintNumber(int number)
{
Console.WriteLine(number);
}
}
class ThreadSample
{
private readonly int _iteration;
public ThreadSample(int iteration)
{
_iteration = iteration;
}
public void CountNumbers()
{
for (int i = 1; i <= _iteration; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(0.5));
Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
}
}
}
}
注释:也可以使用ThreadStart传递参数
线程同步
所谓同步:是指在某一时刻只有一个线程可以访问变量。
如果不能确保对变量的访问是同步的,就会产生错误。
c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在c#中,关键字Lock定义如下:
lock(expression)
{
statement_block
}
expression代表你希望跟踪的对象:
如果你想保护一个类的实例,一般地,你可以使用this;
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
以书店卖书为例
class Program
{
static void Main(string[] args)
{
BookShop book = new BookShop();
//创建两个线程同时访问Sale方法
Thread t1 = new Thread(new ThreadStart(book.Sale));
Thread t2 = new Thread(new ThreadStart(book.Sale));
//启动线程
t1.Start();
t2.Start();
Console.ReadKey();
}
}
class BookShop
{
//剩余图书数量
public int num = 1;
public void Sale()
{
int tmp = num;
if (tmp > 0)//判断是否有书,如果有就可以卖
{
Thread.Sleep(1000);
num -= 1;
Console.WriteLine("售出一本图书,还剩余{0}本", num);
}
else
{
Console.WriteLine("没有了");
}
}
}
运行结果:
从运行结果可以看出,两个线程同步访问共享资源,没有考虑同步的问题,结果不正确。
考虑线程同步,改进后的代码:
class Program
{
static void Main(string[] args)
{
BookShop book = new BookShop();
//创建两个线程同时访问Sale方法
Thread t1 = new Thread(new ThreadStart(book.Sale));
Thread t2 = new Thread(new ThreadStart(book.Sale));
//启动线程
t1.Start();
t2.Start();
Console.ReadKey();
}
}
class BookShop
{
//剩余图书数量
public int num = 1;
public void Sale()
{
//使用lock关键字解决线程同步问题
lock (this)
{
int tmp = num;
if (tmp > 0)//判断是否有书,如果有就可以卖
{
Thread.Sleep(1000);
num -= 1;
Console.WriteLine("售出一本图书,还剩余{0}本", num);
}
else
{
Console.WriteLine("没有了");
}
}
}
}
运行结果:
处理异常
using System;
using System.Threading;
namespace DelegateDemo
{
class Program
{
static void BadFaultyThread()
{
Console.WriteLine("Starting a faulty thread.....");
Thread.Sleep(TimeSpan.FromSeconds(5));
//这个异常主线程无法捕捉到,因为是在子线程抛出的异常。需要在子线程中加入try...catch捕获异常
throw new Exception("BadFaultyThread:Boom!");
}
static void FaultyThread()
{
try
{
Console.WriteLine("Starting a faulty thread...");
Thread.Sleep(TimeSpan.FromSeconds(5));
throw new Exception("FaultyThread:Boom");
}
catch (Exception ex)
{
Console.WriteLine($"Exception handled: {ex.Message}");
}
}
static void Main(string[] args)
{
Thread t = new Thread(FaultyThread);
t.Start();
t.Join();
try
{
t = new Thread(BadFaultyThread);
t.Start();
}
catch (Exception ex)
{
Console.WriteLine("We won't get here");
}
Console.ReadKey();
}
}
}
运行结果: