线程
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ThreadTest{ public static readonly object locker = new object(); public void FuncWithParameter(object obj){ lock(locker){ Console.WriteLint(obj); } } public void FuncWithoutParameter(){ lock(locker){ Console.WriteLine("无参方法") } } } public class Program{ static void Main(string[] args){ ThreadTest test = new ThreadTest(); Thread threadWithParameter = new Thread(new ParameterizedThreadStart(test.FuncWithParameter)); Thread threadWithoutParameter = new Thread(new ThreadStart(test.FuncWithoutParameter)); threadWithParameter.Start("有参方法"); threadWithoutParameter.Start(); } }
|
线程方法
方法 |
描述 |
执行后状态 |
- |
Start |
启动线程 |
|
|
Suspend |
挂起线程 |
|
|
Resume |
继续已挂起的线程 |
|
|
Interrupt |
终止处于WaitSleepJoin线程状态的线程 |
|
|
Join |
阻塞调用线程,直到某个线程终止时为止 |
|
|
Sleep |
将当前线程阻塞指定的毫秒数 |
|
|
Abort |
终止线程 |
|
|
线程状态
状态 |
描述 |
- |
- |
Aborted |
线程已停止; |
|
|
AbortRequested |
线程的Thread.Abort()方法已被调用,但是线程还未停止; |
|
|
Background |
线程在后台执行,与属性Thread.IsBackground有关; |
|
|
Running |
线程正在正常运行; |
|
|
Stopped |
线程已经被停止; |
|
|
StopRequested |
线程正在被要求停止; |
|
|
SuspendRequested |
线程正在要求被挂起,但是未来得及响应; |
|
|
Unstarted |
未调用Thread.Start()开始线程的运行; |
|
|
WaitSleepJoin |
线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态; |
|
|
通过使用AutoResetEvent和ManualResetEvent类来控制循环线程的暂停与继续 保证了每次循环的完整性。
简单来说只有调用Set()方法后才能执行WaitOne()后面的代码,AutoResetEvent和ManualResetEvent分别都有Set()改变为有信号 ,Reset()改变为无信号,WaitOne()将会阻塞当前调用的线程,直到有信号为止,即执行了Set()方法,WaitOne()方法还可以带指定时间的参数。
理解了AutoResetEvent后再理解ManualResetEvent也就不难了,AutoResetEvent与ManualResetEvent的区别是,AutoResetEvent.WaitOne()会自动改变事件对象的状态,即AutoResetEvent.WaitOne()每执行一次,事件的状态就改变一次,也就是从无信号变为有信号,或从有信号变为无信号。而ManualResetEvent则是调用Set()方法后其信号量不会自动改变,除非再设置Reset()方法。
在.Net多线程编程中,AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别。Set方法将信号置为发送状态,Reset方法将信号置为不发送状态,WaitOne等待信号的发送。可以通过构造函数的参数值来决定其初始状态,若为true则非阻塞状态,为false为阻塞状态。如果某个线程调用WaitOne方法,则当信号处于发送状态时,该线程会得到信号, 继续向下执行。其区别就在调用后,AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待.也就是说,AutoResetEvent一次只唤醒一个线程;而ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Program{ static AutoResetEvent autoResetEvent = new AutoResetEvent(false); static void FuncAutoResetEvent(){ autoResetEvent.WaitOne(); Console.WriteLine("线程继续执行......."); } static void Main(string[] args){ Thread thread = new Thread(new ThreadStart(FuncAutoResetEvent)); thread.Start(); ConsoleKeyInfo key = Console.ReadKey(); if(key.Key == ConsoleKey.Y){ autoResetEvent.Set(); } } }
|
C#强制要求代码是线程安全的,即不允许跨线程访问Windows窗体的控件
1 2 3
| Action<int> setVal = (i) => {this.myTxtBox.Text == i.ToString()}; this.myTxtBox.Invoke(setVal,i);
|
.NET三种异步编程模式
异步编程模型(APM)
概念
- .NET 1.0提出了APM(Asynchronous Programming Model)即异步编程模式。
.NET的类库有以BeginXXX和EndXXX类似的方法,就是使用异步编程模型。
- NET Framework很多类也实现了该模式,同时我们也可以自定义类来实现该模式,即在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法,另外委托类型也定义了BeginInvoke和EndInvoke方法。
异步编程模型的本质
- 利用委托和线程池帮助我们实现异步编程模型模式。
- 该模式利用一个线程池线程去执行一个操作,在FileStream类BeginRead方法中就是执行一个读取文件操作,该线程池线程会立即将控制权返回给调用线程,此时线程池线程在后台进行这个异步操作;
- 异步操作完成之后,通过回调函数来获取异步操作返回的结果,此时就是利用委托的机制。
借助委托来实现APM
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
| class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); AsyncCount count = new AsyncCount(Program.count); IAsyncResult result = count.BeginInvoke(1, 100, out int threadId, null, null); Thread.Sleep(1000); Console.WriteLine("回到主线程"); Console.WriteLine("开始等待子线程"); int cou = count.EndInvoke(out threadId, result); Console.WriteLine($"结果是{cou}"); } public static int count(int start, int end, out int threadId) { Console.WriteLine("进入子线程"); int count = 0; for (int i = start; i < end; i++) { Thread.Sleep(100); count += i; } threadId = Thread.CurrentThread.ManagedThreadId; return count; } public delegate int AsyncCount(int start, int end, out int threadId); }
|
基于事件的异步模式(EAP)
Event-based Asynchronous Pattern(EAP),基于事件的异步模式,实际上就是提供了完成任务事件的类,供外部订阅来,实现异步。
还拿数数这个模型来表示一下基于事件的异步模式类。
1 2 3 4 5 6 7
| public class CountModel { public int Count(int start,int end); public void CountAsync(int start,int end); public void CountAsync(int start,int end,object userState); public event CountCompletedEventHandler CountCompleted; }
|
下面为具体的实现
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 29 30 31 32 33 34 35 36 37 38
| public class CountModel { public int Count(int start,int end) { Console.WriteLine("同步线程开始"); int count = 0; for (int i = start; i < end; i++) { Thread.Sleep(1000); count += i; } Console.WriteLine("同步线程完成");
return count; } public void CountAsync(int start,int end,object userState) { Thread thread = new Thread(new ThreadStart(() => { Console.WriteLine("异步线程开始"); int count = 0; for (int i = start; i < end; i++) { Thread.Sleep(1000); count += i; } CountCompleted?.Invoke(this, new CountCompletedEventArgs() { Result = count }); Console.WriteLine("异步线程完成"); })); thread.Start(); } public event CountCompletedEventHandler CountCompleted; public delegate void CountCompletedEventHandler(object sender, CountCompletedEventArgs args); public class CountCompletedEventArgs { public int Result; } }
|
基于任务的异步模式(TAP)
Task-based Asynchronous Pattern(TAP)
TAP与EAP的不同在于,EAP中异步方法返回类型为void,而TAP的返回类型为可等待类型如(Task、Task、ValueTask 和 ValueTask),线程结束后不再通过触发完成事件,二是通过立即返回的Task中获取结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CountModel { public Task<int> CountAsync(int start,int end,object userState) { return Task.Run(()=>{ int count = 0; for (int i = start; i < end; i++) { Thread.Sleep(1000); count += i; } return count; }) } }
|
Task库的使用
优越性:Task >> ThreadPool >> Thread
async/await
用async来修饰一个方法,表明这个方法是异步的,声明的方法的返回类型必须为:void或Task或Task。方法内部必须含有await修饰的方法,如果方法内部没有await关键字修饰的表达式,哪怕函数被async修饰也只能算作同步方法,执行的时候也是同步执行的。
被await修饰的只能是Task或者Task类型,通常情况下是一个返回类型是Task/Task的方法,当然也可以修饰一个Task/Task变量,await只能出现在已经用async关键字修饰的异步方法中。上面代码中就是修饰了一个变量ResultFromTimeConsumingMethod。
三种async异步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public async Task MethodAsync(int input){ await Task.Run(()=>{ Console.WriteLine(input); }) }
public async Task<int> MethodAsync(int input){ return await Task.Run(()=>{ return input; }) }
public async void MethodAsync(int input){ await Task.Run(()=>{ Console.WriteLine(input) }) }
|
- 异步方法的返回类型:void Task Task,后两者为可等待方法。
- 可等待方法如果不进行等待,VS会进行提示。
- 可等待方法需要使用var task = MethodAsync()来获取结果或者等待,或者使用await标记形成新的异步方法。
- 必须成对出现
- await关键字为两线程的分割点,await关键词前的语句在主线程执行,后面另起新线程执行。