System.Threading
Posted Sealee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了System.Threading相关的知识,希望对你有一定的参考价值。
线程:定义为可执行应用程序中的基本执行单元。
应用程序域:一个应用程序内可能有多个线程。
上下文:一个线程可以移动到一个特定的上下文的实体
导入命名空间:
//得到正在执行这个方法的线程 Thread currThread = Thread.CurrentThread; //获取正在承载当前线程的应用程序 AppDomain ad = Thread.GetDomain(); //获取当前操作线程所处的上下文 System.Runtime.Remoting.Contexts.Context ct = Thread.CurrentContext;
线程同步的作用:
同一时间,多个线程都能运行共享的功能块。为了保护资源不被破坏,使用各种原语(比如lock,monitor和[Synchronization]特性或语言关键字支持)来控制线程对它们的访问。
使用System.Threading命名空间中定义的类型,.NET4.0或更高版本的TPL(Task Parallel Library,任务并行库)以及.NET4.5中C#的async和await语言关键字,可以比较放心的操作线程。
想要更好的了解线程的方法,先得知道.NET的委托。
委托
.net委托是一个类型安全的、面向对象的函数指针。当定义了一个.NET委托类型时,作为响应,C#编译器将创建一个派生自System.MulticastDelegate的密封类(派生于System.Delegate) (关于委托的更多介绍以后会写到)
public delegate int BinaryOp(int x,int y); //委托跟类同级 //它能指向任何一个拥有两个整数参数、返回一个整数的方法
例如: 了解委托我们只用了解原型里面的这几个方法就可以了。
public delegate int BinaryOp(int x,int y); //委托跟类同级 //它能指向任何一个拥有两个整数参数、返回一个整数的方法 //public sealed class BinaryOp : MulticastDelegate //剖析委托,下面主要介绍BeginInvoke和EndInvoke里面的参数 //{ // public BinaryOp(object target, uint functionAddress); // public int Invoke(int x,int y); //返回两个整数的和 // public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state); //开始执行异步委托 // public int EndInvoke(IAsyncResult result); //异步结束返回两个整数的和 调用没有返回值的委托就可以不用这个方法 //} class Program { static void Main(string[] args) { Console.WriteLine("****请开始你的表演***"); //输出正在执行中的线程ID Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId); //在同步模式下调用Add() BinaryOp b = new BinaryOp(Add);//指向了Add这个方法 //也能写 b.Invoke(10,10); int answer = b(10, 10); //直到Add()方法完了后,在会继续执行下面的 Console.WriteLine("Doing more work in Main()!"); Console.WriteLine("10+10 is {0}.",answer); Console.WriteLine(); } static int Add(int x, int y) { //输出正在执行中的线程ID Console.WriteLine("Add() invoked onthread {0}",Thread.CurrentThread.ManagedThreadId); //暂定一下,模拟一个耗时的操作 Thread.Sleep(5000); return x + y; } }
结果:
因为是同步模式下调用了Add方法,由于程序中所有的任务都是被主线程执行,所有显示的ID是相同的值
BeginInvoke()和EndInvoke()
C#编译器处理delegate关键字的时候,其动态生成的类定义了两个方法BeginInvoke和EndInvoke
public IAsyncResult BeginInvoke(int x,int y,AsyncCallback cb,object state); //用于异步调用的方法 public int EndInvoke(IAsyncResult result); //用于获取被调用方法的返回值
先来看看BeginInvoke里面的参数。
传入BeginInvoke()的参数的最初集合必须符合C#委托约定(对于BinaryOp,就是两个整型,后两个参数不知道可以给空)。EndInvoke()唯一参数总是IAsyncResult类型。
System.IAsyncResult 接口
查看定义
// 摘要: // 表示异步操作的状态。 [ComVisible(true)] public interface IAsyncResult { // 摘要: // 获取用户定义的对象,它限定或包含关于异步操作的信息。 // // 返回结果: // 用户定义的对象,它限定或包含关于异步操作的信息。 object AsyncState { get; } // // 摘要: // 获取用于等待异步操作完成的 System.Threading.WaitHandle。 // // 返回结果: // 用于等待异步操作完成的 System.Threading.WaitHandle。 WaitHandle AsyncWaitHandle { get; } // // 摘要: // 获取一个值,该值指示异步操作是否同步完成。 // // 返回结果: // 如果异步操作同步完成,则为 true;否则为 false。 bool CompletedSynchronously { get; } // // 摘要: // 获取一个值,该值指示异步操作是否已完成。 // // 返回结果: // 如果操作完成则为 true,否则为 false。 bool IsCompleted { get; }
BeginInvoke()返回的对象实现了IAsyncResult接口,而EndInvoke()需要一个IAsyncResult兼容类型作为它的参数。
所有如果异步调用一个无返回值的方法就不需要EndInvoke方法了。
异步调用方法:
还是上面的列子,修改一下
static void Main(string[] args) { Console.WriteLine("****请开始你的表演***"); //输出正在执行中的线程ID Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId); //在次线程中调用add BinaryOp b = new BinaryOp(Add);//指向了Add这个方法 IAsyncResult ifAR = b.BeginInvoke(10, 10, null, null); Console.WriteLine("Doing more work in Main()!"); //执行完后获取Add()方法的结果 int answer = b.EndInvoke(ifAR); Console.WriteLine("10+10 is {0}.",answer); Console.WriteLine(); }
我们看到了两个不同的ID值,这说明在这个应用程序域中有两个线程正在运行。
同步调用线程
我们一运行,Doing more work inMian() 这个语句就会出现,并没有等到5秒。这样的异步调用中主线程可能会被堵塞,那我们的做异步的优势就不明显了,效率似乎不高,我们需要做另一个同步调用:
IAsyncResult提供给了IsCompleted属性。使用这个成员,调用线程在调用EndInvoke()之前,便能判断异步调用是否真正完成。
如果方法没有完成,IsCompleted方法false。
继续改我们的代码:
static void Main(string[] args) { Console.WriteLine("****请开始你的表演***"); //输出正在执行中的线程ID Console.WriteLine("Main() invoked on thread {0}",Thread.CurrentThread.ManagedThreadId); //在同步模式下调用Add() BinaryOp b = new BinaryOp(Add);//指向了Add这个方法 IAsyncResult ifAR = b.BeginInvoke(10, 10, null, null);
//在Add()方法完成之前会一直显示消息
while (!ifAR.IsCompleted) { Console.WriteLine("Doing more work in Main()!"); Thread.Sleep(1000); } // Console.WriteLine("Doing more work in Main()!"); //我们指定Add()方法调用完成了 int answer = b.EndInvoke(ifAR); //直到Add()方法完了后,在会继续执行下面的 Console.WriteLine("10+10 is {0}.",answer); Console.WriteLine(); }
首先我们会执行BeginInvoke方法,会去执行Add()方法,由于需要5秒完成,所以会输出5此doing,我们打断点可以看出先执行Add()在输出doing,直接运行Doing会先出现一遍。
除了IsCompleted属性之外,IAsyncResult接口还提供了AsyncWaiHandle属性以实现更好的灵活的的等待逻辑。返回WaitHandle类型的实例,改实例公开了一个WaitOne()的方法。
此方法可以指定最长等待时间如果超时,返回false.
while (!ifAR.AsyncWaitHandle.WaitOne(1000,true)) //没有完成异步会一直执行 { Console.WriteLine("Doing more work in Main()!"); }
这种的形式比上面的好理解些。
AsyncCallback 委托的作用
不通过一个委托开确定异步调用方法执行是否结束,而是在任务完成时由次线程主动通知调用线程方式。
调用BeginInvoke()时提供一个System.AsyncCallback委托的实例作为参数,默认是Null。只要提供了此对象,异步调用完成,自动调用指定的方法。
例如:
class Program { public static bool isDone = false; static void Main(string[] args) { Console.WriteLine("**** AsyncCallbackDelegate 列子 ******"); Console.WriteLine("Main() invoke on thread {0}.",Thread.CurrentThread.ManagedThreadId); BinaryOp b = new BinaryOp(Add); IAsyncResult iftAR = b.BeginInvoke(10,10,new AsyncCallback(AddComplete),null); while (!isDone) { Thread.Sleep(1000); Console.WriteLine("Working ..."); } Console.ReadLine(); } static int Add(int x, int y) { //输出正在执行中的线程ID Console.WriteLine("Add() invoked onthread {0}",Thread.CurrentThread.ManagedThreadId); //暂定一下,模拟一个耗时的操作 Thread.Sleep(5000); return x + y; }
//当异步完成后会主动调用此方法 static void AddComplete(IAsyncResult itfAR) { Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId); Console.WriteLine("完成 了"); isDone = true; } }
AsyncResult 类的作用
此类在 System.Remoting.Messaging命名空间下。该类的只读属性 AsyncDelegate 返回了别处创建的原始异步委托引用。
我们向获取在Main()中的BinaryOp委托对象的引用,只需要把由AsyncDelegate 属性返回的System.Object 类型转成BinaryOp类型就可以了
把AddComplete方法加点代码:
static void AddComplete(IAsyncResult itfAR) { Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId); Console.WriteLine("完成 了"); AsyncResult ar = (AsyncResult)itfAR; BinaryOp b = (BinaryOp)ar.AsyncDelegate; //获取异步对象 Console.WriteLine("10+10 is {0}",b.EndInvoke(itfAR)); isDone = true; }
传递和接收自定义状态数据
异步委托注意最后一个地方就是BeginInvoke()方法的最后一个参数(默认为null)。该参数允许从主线程传递额外的状态信息给回调函数。因为这个参数类型是System.Object,所以可以传递任何回调方法所希望的类型的数据。
例如:
BinaryOp b = new BinaryOp(Add); IAsyncResult iftAR = b.BeginInvoke(10,10,new AsyncCallback(AddComplete),"谢谢你的表演");
为了获取该数据,使用传入IAsyncResult参数的AsyncState属性。 需要转型
static void AddComplete(IAsyncResult itfAR) { Console.WriteLine("AddComplete() Invoke on thred {0}",Thread.CurrentThread.ManagedThreadId); Console.WriteLine("完成 了"); AsyncResult ar = (AsyncResult)itfAR; BinaryOp b = (BinaryOp)ar.AsyncDelegate; Console.WriteLine("10+10 is {0}",b.EndInvoke(itfAR)); string msg = (string)itfAR.AsyncState; //获取异步操作信息 Console.WriteLine(msg); isDone = true; }
委托就介绍到这里。
进入主题:
System.Threading 命名空间 学习一个命名空间,就是学习里面包含类,学习类掌握这个类有什么属性和方法即可
这个命名空间提供了许多类型用来构建多线程应用程序。
构造函数:
System.Threading命名空间中的部分类型
Interlocked 为被多个线程共享访问的类型提供原子操作
Monitor 使用锁定和等待信号来同步线程对象。C#的lock关键字在后台使用的就是Monitor对象
Mutex 互斥体,可用于应用程序域边界之间的同步
ParameterizedThreadStart 委托,它允许线程调用包含任意多个参数的方法
Semaphore 用于限制对一个资源或一类资源的并发访问的线程数量
Thread 代表CLR(运行库)中执行的线程。使用这个类型,能够在初始的应用程序域中创建额外的线程
ThreadPool 用于和一个线程中(由CLR维护的)线程池交互
ThreadPriority 代表了线程调度的优先级(Highet、Normal)等
ThreadStart 该委托用于定义一个线程调用的方法,和ParameterizedThreadStart 委托不同,这个方法的目标必须符合一种固定的原型
ThreadState 代表线程处于的状态(Running、Aborted等)
Timer 提供以指定的时间间隔执行方法的机制
TimerCallback 该委托类型应于Timer类型一起使用
更详细的了解去官网看
System.Threading.Thread 类
定义:一个面向对象的包装器,包装特定应用程序域中某个执行单元。
主要的静态成员
CurrentContext 只读属性,返回当前线程的上下文
CurrentThread 只读属性,返回当前线程的引用
GetDomain()和GetDomainID() 返回当前应用程序域的引用或当前线程正在运行的域的ID
Sleep() 将当前线程挂起指定的时间
主要的实例级成员
IsAlive 返回布尔值,指示线程是否开始了
IsBackground 获取或设置一个值,指示线程是否为后台线程
Name 给线程指定的友好的名字
Priority 获取或设置线程的调度优先级。它是ThreadPriority枚举中的值之一
ThreadState 获取当前线程的状态。它是ThreadState枚举中的值之一
Abort 通知CLR尽快终止本线程
Interrupt 中断当前线程,唤醒处于等待中的线程
Join 阻塞调用线程,直到某个(调用Join()的)线程终止为止
Resume 使已挂起的线程继续执行
Start 通知CLR尽快执行本线程
Suspend() 挂起当前线程如果线程已挂起,则改方法不起作用
获得当前执行线程的统计信息
static void Main(string[] args)
{
Console.WriteLine("请开始你的表演");
Thread primaryThread = Thread.CurrentThread;
primaryThread.Name = "主线程";
Console.WriteLine("当前应用域的名字:{0}",Thread.GetDomain().FriendlyName);
Console.WriteLine("上下文的ID:{0}",Thread.CurrentContext.ContextID);
Console.WriteLine("线程的名字:{0}",primaryThread.Name);
Console.WriteLine("线程是否开始:{0}",primaryThread.IsAlive);
Console.WriteLine("线程的优先级:{0}",primaryThread.Priority);
Console.WriteLine("当前线程的状态:{0}",primaryThread.ThreadState);
Console.ReadLine();
}
Name 属性
不设置将返回空字符串,设置了可以在调试的时候很快找到你想调试的线程。
Priority 属性 谨慎使用
默认情况,所有线程的优先级都是Normal级别。可以在线程的生命周期的任何时候,可以使用ThreadPriority 枚举来修改线程的优先级,且值必须是System,Threading.ThreadPriority枚举中的一个。
// 摘要: // 指定 System.Threading.Thread 的调度优先级。 [Serializable] [ComVisible(true)] public enum ThreadPriority { // 摘要: // 可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之后。 Lowest = 0, // // 摘要: // 可以将 System.Threading.Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。 BelowNormal = 1, // // 摘要: // 可以将 System.Threading.Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。默认情况下,线程具有 // Normal 优先级。 Normal = 2, // // 摘要: // 可以将 System.Threading.Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。 AboveNormal = 3, // // 摘要: // 可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之前。 Highest = 4, }
构造函数使用:
手动创建此线程
①创建一个方法作为新线程的入口点。
②创建一个 ParameterizedThreadStart(或者ThreadStart)委托,并把上一步所定义方法的地址传给委托的构造函数。
③创建一个Thread对象,并把ParameterizedThreadStart或者ThreadStart 委托作为构造函数的参数
④建立任意初始化线程的特性(名称,优先级)
⑤调用Thread.Start()方法, Start()方法有重载,一个是无参数代表 ThreadStart 委托 不能传递参数,Start(object o) 可以传递一个Object类型参数,代表ParameterizedThreadStart
委托,可以传递参数
public delegate void ThreaStarts(); //定义一个委托 写不写都可以,因为有个ThreadStart 委托类 public class Program { static void Main(string[] args) { Thread th = new Thread(ExecuteInForForeground);//自己写个委托 注意:这个不带参数 简单的写法,不定义委托,直接给方法,自己会匹配 //Thread th1 = new Thread(new ThreadStart(ExecuteInForForeground)); //利用命名空间自带的委托 th.Start(); Thread.Sleep(1000); Console.WriteLine("Main thread ({0}) exiting...", Thread.CurrentThread.ManagedThreadId); } private static void ExecuteInForForeground() //配合委托 { DateTime dt = DateTime.Now; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); //用来计算运行时间 Console.WriteLine("Thread {0}:{1},Priority {2}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState, Thread.CurrentThread.Priority); do { Console.WriteLine("Thread {0}: Elapsed {1:N2} seconds", Thread.CurrentThread.ManagedThreadId, sw.ElapsedMilliseconds / 1000.0); Thread.Sleep(500); //.05s挂起一次 } while (sw.ElapsedMilliseconds <= 5000); //5s sw.Stop(); } }
public delegate void ParameterizedThreadStarts(object o); public class Program { static void Main(string[] args) { Thread th = new Thread(ExecuteInForForeground);//自己写个委托 注意:这个不带参数 //Thread th = new Thread(new ParameterizedThreadStart(ExecuteInForForeground)); th.Start(4000); //传递参数到我们的委托方法里面 Thread.Sleep(1000); Console.WriteLine("Main thread ({0}) exiting...", Thread.CurrentThread.ManagedThreadId); } private static void ExecuteInForForeground(object o) //配合委托 { int i; try { i = (int)o; } catch (InvalidCastException) { i = 5000; } DateTime dt = DateTime.Now; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); Console.WriteLine("Thread {0}:{1},Priority {2}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState, Thread.CurrentThread.Priority); do { Console.WriteLine("Thread {0}: Elapsed {1:N2} seconds", Thread.CurrentThread.ManagedThreadId, sw.ElapsedMilliseconds / 1000.0); Thread.Sleep(500); } while (sw.ElapsedMilliseconds <= i); //5s sw.Stop(); } }
总结:启用线程我们需要知道去执行哪个方法,这个方法不能有返回值,可以有参可以无参。
AutoResetEvent 类
强制线程等待,直到其他线程结束。
方法:
向构造函数中传入false,表示尚未收到通知。然后在你需要等待的地方调用WaitOne()方法。你的动作做完了 调用 Set()方法就可以了。
class Program { private static AutoResetEvent waitHandle = new AutoResetEvent(false); static void Main(string[] args) { Console.WriteLine("请开始你的表演"); Console.WriteLine("线程的ID:{0}", Thread.CurrentThread.ManagedThreadId); Thread th = new Thread(Add); AddParams ad = new AddParams(10,10); th.Start(ad); //等待,直到收到通知 没有收到通知下面的语句不会执行 waitHandle.WaitOne(); Console.WriteLine("做完了"); Console.ReadLine(); } static void Add(object data) { if (data is AddParams) { Console.WriteLine("添加里面的线程ID为:{0}",Thread.CurrentThread.ManagedThreadId); AddParams ap = data as AddParams; Console.WriteLine("{0}+ {1} = {2}",ap.a,ap.b,ap.a+ap.b); //通知其他线程,该线程已结束 waitHandle.Set(); } } } class AddParams { public int a, b; public AddParams(int num1,int num2) { a = num1; b = num2; } }
前台线程和后台线程
两者区别:
只有当所有的前台线程全部执行完毕后,应用程序才能够退出。而对于后台线程,当应用程序退出的时候,后台线程会被强制终止。
前台线程和后台线程并不等同于主线程和工作者线程。默认情况下,所有通过Thread.Start()方法创建的线程都自动成为前台线程。只要把IsBackground属性设置为True就可以讲线程配置成后台线程。
static void Main(string[] args) { Console.WriteLine("请开始你的表演"); Console.WriteLine("线程的ID:{0}", Thread.CurrentThread.ManagedThreadId); Printer pr = new Printer(); Thread th = new Thread(pr.PrintNumbers); th.IsBackground = true以上是关于System.Threading的主要内容,如果未能解决你的问题,请参考以下文章
持续发生的 mscorlib.dll 中发生 System.Threading.ThreadAbortException
无法将类型“string”隐式转换为“System.Threading.Tasks.Task<string>”
错误 CS0433 System.Threading 和 mscorlib 中都存在“任务”类型
是所有用户之间共享的System.Threading.Thread.CurrentPrincipal? [关闭]