最近花了近两周时间读完了C#本质论,这本书非常喜欢,但是到后面关于多线程和同步这块,读起来就感觉有些困难了,所以做了笔记,一方面防止忘记,另一方法如果有不正确的地方,十分欢喜各位前辈不吝赐教
什么是单线程
通过一个控制台程序来认识单线程
static void Main(string[] args){ var mainThread = Thread.CurrentThread;}
在Console.WriteLine处添加一个断点,查看主线程属性
ApartmentSate:msdn的大致意思,相同对单元状态的线程之间可以相互访问对象,然而在.net中由clr以线程安全的方式管理所有共享资源
CurrentCulture和CurrentUICulture表示区域信息
ExecutionContext:封装线程相关的上下文信息
IsAlive:如果此线程已启动并且尚未正常终止或中止,则为 true;否则为 false
IsBackground:表示是否是后台线程
IsThreadPoolThread:表示是否是线程池线程
ManagedThreadId:托管线程的唯一标识符
更多可查msdn
小结:
关于线程的定义很多地方都有的,我想举一个例子,很多时候,我们人就是一个线程,早上起床,吃早饭,上班,下班......这一系列事情有序执行就是一个单线程,但是有的时候,一边听歌,一边看小说实际上就开启了第二个线程了,假如此时再写代码,那就是开启第三个线程了
使用Thread创建一个线程
const int Repetitions = 100;static void Main(string[] args){ ThreadStart threadStart = DoWork; Thread thread = new Thread(threadStart); thread.Start(); //Main线程启动一个循环 for (int count = 0; count < Repetitions; count++) { Console.Write('-'); } Console.WriteLine("(主线程最后一个语句...)");}static void DoWork(){ for (int count = 0; count < Repetitions; count++) { Console.Write("+"); }}
Ctrl+F5运行,可以看到,新创建的线程和Main线程中的循环是同步执行的(多启动几次,会有不一样的发现哦!)
那么问题来了,我们创建的线程执行完了吗?程序到底什么时候结束?为什么主线程最后一句话执行完了,创建的线程还在控制台输出?
修改一下程序,Ctrl+F5,多启动几次,会有不一样的发现哦!
const int Repetitions = 100;static int index_thread = 0;static int index_main = 0;static void Main(string[] args){ ThreadStart threadStart = DoWork; Thread thread = new Thread(threadStart); thread.Start(); //Main线程启动一个循环 for (int count = 0; count < Repetitions; count++) { index_main++; Console.Write('-'); } Console.WriteLine($"\nindex_thread:{index_thread}"); Console.WriteLine($"index_main:{index_main}"); Console.WriteLine("(主线程最后一个语句...)");}static void DoWork(){ for (int count = 0; count < Repetitions; count++) { index_thread++; Console.Write("+"); if (count == Repetitions - 1) { Debug.Write("我创建的线程执行完成了.....................................\n"); } }}
假如你是直接按F5,可以在Visual Studio输出栏看到
结论:
1.操作系统在所有前台线程(主线程和新创建的线程都是前台线程)结束后终止进程,虽然在控制台中输出的index_thread不总是100
2.主线程以外的线程执行情况是不确定的,
3.实际上,主线程会等待所有子线程(前台线程)结束后,结束主线程,关闭进程,结束程序
4.由于子主线程执行情况的不确定性,在主线程输出index_thread的时候,可能子线程循环结束了,也可能没结束,所以导致结果总是不为100
通过Join方法阻塞主线程,等待子线程执行结束
//省略部分代码thread.Join();Console.WriteLine($"\nindex_thread:{index_thread}");
这样,就可以保证在此之后,子线程已经运行结束了,每次输出的结果都为100
使用线程池
const int Repetitions = 1000;static int index_thread = 0;static int index_main = 0;static void Main(string[] args){ WaitCallback waitCallBack = DoWork; ThreadPool.QueueUserWorkItem(waitCallBack, '+'); //Main线程启动一个循环 for (int count = 0; count < Repetitions; count++) { index_main++; Console.Write('-'); } Console.WriteLine($"\nindex_thread:{index_thread}"); Console.WriteLine($"index_main:{index_main}"); Console.WriteLine("主线程最后一个语句");}private static void DoWork(object ch){ for (int count = 0; count < Repetitions; count++) { index_thread++; Console.Write(ch); if (count == Repetitions - 1) { Debug.Write("我创建的线程执行完成了.....................................\n"); } }}
优点:
1.解决线程太多造成的性能方面的负面影响
2.高效的利用处理器
2.结合lambda使用委托,代码可以更精简
注意点
1.使用线程池创建的线程都是后台线程
2.不要使用线程池运行时间特别长的任务,尽量不要I/O受限
异步任务
static void Main(string[] args){ Task task = Task.Run(() => { var t = Thread.CurrentThread; for (int count = 0; count < Repetitions; count++) { index_thread++; Console.Write('+'); } }); for (int count = 0; count < Repetitions; count++) { index_main++; Console.Write('-'); } //类似THread.Join方法 task.Wait(); Console.WriteLine($"\nindex_thread:{index_thread}"); Console.WriteLine($"index_main:{index_main}"); Console.WriteLine("Over"); Console.ReadLine();}
Task是.Net Framwwork4引入的一个类库,它在使用上比Thread简单了,可控性又THreadPool强了,默认情况下,它也是从线程池中请求一个线程来执行任务.
与ThreadPool相同的是,当创建时(调用Run)启动,与Thread相同的是,可以通过Wait()方法阻塞上下文线程(主线程)等待任务执行完成
带返回值的异步任务
static void Main(string[] args){ Tasktask = Task.Run(() => "string 类型 返回值"); for (int i = 0; i < 1000; i++) { if (task.IsCompleted) { Console.Write('任务完成了'); break; } Console.Write('.'); } Console.WriteLine(task.Result);}
泛型的Task表示该任务具有返回值,IsCompleted表示任务是否完成
要注意的是,调用Result属性的时候,会阻塞上下文进程(内部执行Wait())