时间:2021-05-20
线程是一个独立的运行单元,每个进程内部都有多个线程,每个线程都可以各自同时执行指令。每个线程都有自己独立的栈,但是与进程内的其他线程共享内存。但是对于.NET的客户端程序(Console,WPF,WinForms)是由CLR创建的单线程(主线程,且只创建一个线程)来启动。在该线程上可以创建其他线程。
图:
多线程由内部线程调度程序管理,线程调度器通常是CLR委派给操作系统的函数。线程调度程序确保所有活动线程都被分配到合适的执行时间,线程在等待或阻止时 (例如,在一个独占锁或用户输入) 不会消耗 CPU 时间。
在单处理器计算机上,线程调度程序是执行时间切片 — 迅速切换每个活动线程。在 Windows 中, 一个时间片是通常数十毫秒为单位的区域 — — 相比来说 线程间相互切换比CPU更消耗资源。在多处理器计算机上,多线程用一种混合的时间切片和真正的并发性来实现,不同的线程会在不同的cpu运行代码。
如:
using System;using System.Threading;class ThreadTest{ static void Main() { Thread t = new Thread (Write2); // 创建线程t t.Start(); // 执行 Write2() // 同时执行主线程上的该方法 for (int i = 0; i < 1000; i++) Console.Write ("1"); } static void Write2() { for (int i = 0; i < 1000; i++) Console.Write ("2"); }}输出
111122221122221212122221212......
在主线程上创建了一个新的线程,该新线程执行WrWrite2方法,在调用t.Start()时,主线程并行,输出“1”。
图:
线程Start()之后,线程的IsAlive属性就为true,直到该线程结束(当线程传入的方法结束时,该线程就结束)。
如:
static void Main() { new Thread (Go).Start(); // 创建一个新线程,并调用Go方法 Go(); // 在主线程上调用Go方法} static void Go(){ // 声明一个本地局部变量 cycles for (int cycles = 0; cycles < 5; cycles++) Console.Write ('N');}输出
NNNNNNNNNN (共输出10个N)
在新线程和主线程上调用Go方法时分别创建了变量cycles,这时cycles在不同的线程栈上,所以相互独立不受影响。
图:
如果不同线程指向同一个实例的引用,那么不同的线程共享该实例。
如:
class ThreadTest{ //全局变量 int i; static void Main() { ThreadTest tt = new ThreadTest(); // 创建一个ThreadTest类的实例 new Thread (tt.Go).Start(); tt.Go(); } // Go方法属于ThreadTest的实例 void Go() { if (i==1) { ++i; Console.WriteLine (i); } }}输出
2
新线程和主线程上调用了同一个实例的Go方法,所以变量i共享。
静态变量也可以被多线程共享
class ThreadTest { static int i; // 静态变量可以被线程共享 static void Main() { new Thread (Go).Start(); Go(); } static void Go() { if (i==1) { ++i; Console.WriteLine (i); } }}输出
2
如果将Go方法的代码位置互换
static void Go() { if (i==1) { Console.WriteLine (i);++i;} }输出
1 1(有时输出一个,有时输出两个)
如果新线程在Write之后,done=true之前,主线程也执行到了write那么就会有两个done。
不同线程在读写共享字段时会出现不可控的输出,这就是多线程的线程安全问题。
解决方法: 使用排它锁来解决这个问题--lock
class ThreadSafe { static bool done; static readonly object locker = new object(); static void Main() { new Thread (Go).Start(); Go(); } static void Go() { //使用lock,确保一次只有一个线程执行该代码 lock (locker) { if (!done) { Console.WriteLine ("Done"); done = true; } } }}当多个线程都在争取这个排它锁时,一个线程获取该锁,其他线程会处于blocked状态(该状态时不消耗cpu),等待另一个线程释放锁时,捕获该锁。这就保证了一次
只有一个线程执行该代码。
Join可以实现暂停另一个线程,直到调用Join方法的线程结束。
static void Main(){ Thread t = new Thread (Go); t.Start(); t.Join(); Console.WriteLine ("Thread t has ended!");} static void Go(){ for (int i = 0; i < 1000; i++) Console.Write ("y");}输出
yyyyyy..... Thread t has ended!
线程t调用Join方法,阻塞主线程,直到t线程执行结束,再执行主线程。
Sleep:暂停该线程一段时间
Thread.Sleep (TimeSpan.FromHours (1)); // 暂停一个小时Thread.Sleep (500); // 暂停500毫秒Join是暂停别的线程,Sleep是暂停自己线程。上面的例子是使用Thread类的构造函数,给构造函数传入一个ThreadStart委托。来实现的。
public delegate void ThreadStart();然后调用Start方法,来执行该线程。委托执行完该线程也结束。
如:
class ThreadTest{ static void Main() { Thread t = new Thread (new ThreadStart (Go)); t.Start(); // 执行Go方法 Go(); // 同时在主线程上执行Go方法 } static void Go() { Console.WriteLine ("hello!"); }}多数情况下,可以不用new ThreadStart委托。直接在构造函数里传入void类型的方法。
Thread t = new Thread (Go);使用lambda表达式
static void Main(){ Thread t = new Thread ( () => Console.WriteLine ("Hello!") ); t.Start();}默认情况下创建的线程都是Foreground,只要有一个Foregournd线程在执行,应用程序就不会关闭。
Background线程则不是。一旦Foreground线程执行完,应用程序结束,background就会强制结束。
可以用IsBackground来查看该线程是什么类型的线程。
此时并不能在Main方法里捕获线程Go方法的异常,如果是Thread自身的异常可以捕获。
正确捕获方式:
public static void Main(){ new Thread (Go).Start();} static void Go(){ try { // ... throw null; // 这个异常会被下面捕获 // ... } catch (Exception ex) { // ... }}当创建一个线程时,就会消耗几百毫秒cpu,创建一些新的私有局部变量栈。每个线程还消耗(默认)约1 MB的内存。线程池通过共享和回收线程,允许在不影响性能的情况下启用多线程。
每个.NET程序都有一个线程池,线程池维护着一定数量的工作线程,这些线程等待着执行分配下来的任务。
线程池线程注意点:
1 线程池的线程不能设置名字(导致线程调试困难)。
2 线程池的线程都是background线程
3 阻塞一个线程池的线程,会导致延迟。
4 可以随意设置线程池的优先级,在回到线程池时改线程就会被重置。
通过Thread.CurrentThread.IsThreadPoolThread.可以查看该线程是否是线程池的线程。
使用线程池创建线程的方法:
TPL
Framework4.0下可以使用Task来创建线程池线程。调用Task.Factory.StartNew(),传递一个委托
Task.Factory.StartNew 返回一个Task对象。可以调用该Task对象的Wait来等待该线程结束,调用Wait时会阻塞调用者的线程。
QueueUserWorkItem没有返回值。使用 QueueUserWorkItem,只需传递相应委托的方法就行。
static void Main(){ //Go方法的参数data此时为空 ThreadPool.QueueUserWorkItem (Go); //Go方法的参数data此时为123 ThreadPool.QueueUserWorkItem (Go, 123); Console.ReadLine();} static void Go (object data) { Console.WriteLine ("Hello from the thread pool! " + data);}委托异步可以返回任意类型个数的值。
使用委托异步的方式:
如:
static void Main(){ Func<string, int> method = Work; IAsyncResult cookie = method.BeginInvoke ("test", null, null); // // ... 此时可以同步处理其他事情 // int result = method.EndInvoke (cookie); Console.WriteLine ("String length is: " + result);} static int Work (string s) { return s.Length; }使用回调函数来简化委托的异步调用,回调函数参数为IAsyncResult类型
static void Main(){ Func<string, int> method = Work; method.BeginInvoke ("test", Done, method); // ... //并行其他事情} static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie){ var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result);}使用匿名方法
Func<string, int> f = s => { return s.Length; }; f.BeginInvoke("hello", arg => { var target = (Func<string, int>)arg.AsyncState; int result = target.EndInvoke(arg); Console.WriteLine("String length is: " + result); }, f);Thread构造函数传递方法有两种方式:
public delegate void ThreadStart();public delegate void ParameterizedThreadStart (object obj);所以Thread可以传递零个或一个参数,但是没有返回值。
Lambda简洁高效,但是在捕获变量的时候要注意,捕获的变量是否共享。
如:
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();输出
0223447899
因为每次循环中的i都是同一个i,是共享变量,在输出的过程中,i的值会发生变化。
解决方法-局部域变量
for (int i = 0; i < 10; i++){ int temp = i; new Thread (() => Console.Write (temp)).Start();}这时每个线程都指向新的域变量temp(此时每个线程都有属于自己的花括号的域变量)在该线程中temp不受其他线程影响。
委托可以有任意个传入和输出参数。以Action,Func来举例。
使用回调函数获取返回值
static void Main(){ Func<string, int> method = Work; method.BeginInvoke ("test", Done, null); // ... //并行其他事情} static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie){ var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result);}EndInvoke做了三件事情:
Task泛型允许有返回值。
如:
static void Main(){ // 创建Task并执行 Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http:///threading/以上就是C# 线程相关知识总结的详细内容,更多关于C# 线程的资料请关注其它相关文章!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
什么是委托?之前写了事件的介绍:这里也把委托相关知识也总结一下。委托是c#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针声明委托的方式:delega
本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保证各个线程的数据不交叉;一是多个线程间如何共享数据,保证数据的一致性。线程范围
C#是一门支持多线程的语言,因此线程的使用也是比较常见的。由于线程的知识在Win32编程的时候已经说得过多,所以在.Net中很少介绍这部分(可能.Net不觉得这
本文实例讲述了C#获取进程或线程相关信息的方法。分享给大家供大家参考。具体实现方法如下:usingSystem;usingSystem.Collections.
相信很多人都了解c#语言,但是对于c#语言编写应用程序的经验不够多,所以经常为没有实例练习而烦恼吧。今天小编给大家介绍下C#里的多线程技术。主要是让大家学会线程