线程相关
关于线程的概念很多,简单的说,线程是程序执行流的最小单元,如果把进程比作一条河流,那么线程就是河流的一条小支流。他是独立执行,却能对主进程有影响。
常识
1. 前台线程和后台线程:通过Thread类新建线程 thread1 默认为前台线程。当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常。将前台线程转后台线程,只需thread1.IsBackground = true2. 挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的情况,在实际应用中应该尽量少用。
3. 阻塞线程:Join,阻塞调用线程,直到该线程终止。
4. 终止线程:Abort:抛出 ThreadAbortException 异常让线程终止,终止后的线程不可唤醒。Interrupt:抛出 ThreadInterruptException 异常让线程终止,通过捕获异常可以继续执行。
5. 线程优先级:AboveNormal BelowNormal Highest Lowest Normal,默认为Normal。
单线程与多线程
线程池:线程池线程默认为后台线程(IsBackground)
由于线程的创建和销毁需要耗费一定的开销,过多的使用线程会造成内存资源的浪费,出于对性能的考虑,于是引入了线程池的概念。线程池维护一个请求队列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带来的开销。
同步与异步
同步的使用场景:多个线程同时访问一块数据,也叫共享区。对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况。比如数据库中的脏读。但是,多个线程同时访问一块数据,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。
创建线程实例
创建一个最简单的单线程:
//实例化线程 new System.Threading.Thread(Function).Start(); private void Function() { MessageBox.Show("线程"); }
这种线程调用方式,仅适用于无参方法调用,同时,可以限制线程使用的堆栈大小,只需在Thread(Function,int)增加字节为单位的整形参数。同时,这种方式,不需要去关闭线程,垃圾回收机制会将其自动回收,可好比这就是一个参数对象。
实现同样效果还可以这样创建线程:
Thread thread1 = new Thread(Function); thread1.Start();
但是这个需要注意的是,一般在线程不用时,需要用线程的Abort()方法结束线程。
此外,还可以这样写
//利用委托 Thread thread2 = new Thread(delegate () { MessageBox.Show("线程委托"); }); thread2.Start(); //Lambda表达式的形式 Thread thread3 = new Thread(() => { MessageBox.Show("Lambda表达式"); }); thread3.Start();
本着学习的态度来找知识的话,现在应该会提出,如果要传参数,那应该怎么写呢,下面就说一下创建有参数的线程方法。
线程传参
首先,最简单的,就是利用线程的公共API传参(也就是Start()方法)
/// <summary> /// 调用方法 /// </summary> /// <param name="e">参数对象</param> private void Function(object e) { MessageBox.Show(e.ToString()); } //调用 Thread thread = new Thread(Function); thread.Start("线程传参");
这种传参方式有时可能不太实用(参数只有一个),那么我们还可以自己定义一个类对象,来实现传参
public class ThreadEntry { private int intPara; private string strPara; public ThreadEntry(int intPara, string strPara) { this.intPara = intPara; this.strPara = strPara; } /// <summary> /// 调用方法 /// </summary> public void Method() { MessageBox.Show(string.Format(strPara, intPara)); } } //调用 Thread thread = new Thread(new ThreadEntry(1,"第{0}种类传参").Method); thread.Start();
除此之外,还可以提前定义一个线程的抽象类,来实现参数传递方法,这样调用起来更方便。尽管两种方法的可拓展性都挺高的,但是用抽象类的办法,还是要好一点,因为在实例化对象时,会少一个层级。
首先,我们需要定义一个自己的线程抽象类
abstract class MyThread { Thread thread = null; abstract public void run(); public void start() { if (thread == null) thread = new Thread(run); thread.Start(); } }
之后再定义一个它的子类
class ParaThread : MyThread { private int intPara; private string strPara; public ParaThread(int intPara, string strPara) { this.intPara = intPara; this.strPara = strPara; } override public void run() { MessageBox.Show(string.Format(strPara, intPara)); } }
这里只用了两个参数的构造函数,所以只能传两个参数,如果说你想传更多的参数,只需要对构造函数重写就行了,之前的对象类,需要更多的传参,也是重写就行了。
核心东西写完后,简单调用一下就好了
ParaThread thread = new ParaThread(2, "第{0}种类传参"); thread.start();
线程传参方式很多,这里只是对常用的进行了举例。
异步线程
对于异步线程,前面已做过简要的讲解,这里便举一个实例,供参考学习。后面还有一个task任务也是属于异步线程的范畴。
private Control _control; private Thread beginInvokeThread; public delegate void beginInvokeDelegate(); public event beginInvokeDelegate Run;//委托事件 /// <summary> /// 执行事件 /// </summary> /// <returns>返回执行结果:true</returns> private bool Do() { if (this.Run != null) RaiseEvent(Run); return true; } /// <summary> /// 事件处理函数 /// </summary> /// <param name="handler">处理</param> private void RaiseEvent(beginInvokeDelegate handler) { if (handler != null) { beginInvokeDelegate beginInvoke = handler; beginInvoke(); } } /// <summary> /// 异步线程入口 /// </summary> /// <param name="control">作用控件</param> /// <param name="run">执行函数</param> public void DoBeginInvoke(Control control, beginInvokeDelegate run) { Run = run; _control = control; //开启异步线程 System.Threading.ThreadStart s = new System.Threading.ThreadStart(new System.Threading.ThreadStart(Result)); beginInvokeThread = new System.Threading.Thread(s); beginInvokeThread.Name = "异步线程"; beginInvokeThread.Start(); } /// <summary> /// 中断执行 /// </summary> private void EndProcess() { if (beginInvokeThread.IsAlive) beginInvokeThread.Abort();//结束线程 } /// <summary> /// 执行结果处理 /// </summary> private void Result() { bool ok = Do(); _control.BeginInvoke(new System.Threading.ThreadStart(delegate() { if (ok) { EndProcess(); } })); }
这是一个异步线程类,结构比较清晰,很容易就能明白,只要知道了其中的原理,其实可以写的更加简化。这里需要注意的是,这是一个UI异步线程,如果在异步线程还未执行完成时,强制关掉了窗体,可能会触发异常,所以关闭窗体时调用EndProcess()方法,也可尝试将线处理为后台线程,即IsBackground 设为true。
同步线程
同步与异步在程序上的差别不是很大,基本上就是将上面的异步线程实例中的BeginInvoke更换成Invoke便可以实现同步线程的效果。
多线程
线程池[ThreadPool]
这是一种相对较简单的方法,适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程) ,明显缺点就是对创建的线程不能加以控制及设置其优先级。由于每个进程只有一个线程池,所以ThreadPool类的成员函数都为static。
核心函数介绍:
//调用成功则返回true,它的另一个重载函数类似,只是委托不带参数而已 public static bool QueueUserWorkItem( WaitCallback callBack,//要创建的线程调用的委托 object state //传递给委托的参数 );
public static RegisteredWaitHandle RegisterWaitForSingleObject( WaitHandle waitObject,// 要注册的 WaitHandle WaitOrTimerCallback callBack,// 线程调用的委托 object state,//传递给委托的参数 int TimeOut,//超时,单位为毫秒, bool executeOnlyOnce file://是否只执行一次 );
public delegate void WaitOrTimerCallback( object state,//也即传递给委托的参数 bool timedOut//true表示由于超时调用,反之则因为waitObject );
使用实例:
首先定义一个多线程操作类
public class Multithreading { public static int iCount = 0; public static int iMaxCount = 0; public ManualResetEvent rEvent; public Multithreading(int iMaxCount, ManualResetEvent rEvent) { this.iMaxCount = count; this.rEvent = rEvent; } public void DoProcess(object i) { Console.WriteLine("Thread操作[" + i.ToString() + "]"); Thread.Sleep(1000); //Interlocked.Increment()操作是一个原子操作,作用是:iCount++ 具体请看下面说明 //原子操作,就是不能被更高等级中断抢夺优先的操作。你既然提这个问题,我就说深一点。 //由于操作系统大部分时间处于开中断状态, //所以,一个程序在执行的时候可能被优先级更高的线程中断。 //而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。 //就是不能被中断的操作。 Interlocked.Increment(ref iCount); if (iCount == iMaxCount) { Console.WriteLine("发出结束信号!"); //将事件状态设置为终止状态,允许一个或多个等待线程继续。 eventX.Set(); } } }
有了这个操作类,我们就可以将想要传的参数通过操作类的构造函数传递过去,在方法中使用。有了操作类,接下来就可以创建线程池,实现简单的多线程处理效果了。
//新建ManualResetEvent对象并且初始化为无信号状态 ManualResetEvent rEvent = new ManualResetEvent(false); ThreadPool.SetMaxThreads(3, 3); int executeCount = 10; Multithreading t = new Multithreading(executeCount, rEvent); for (int i = 0; i < executeCount; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(t.DoProcess), i); } //WaitOne 阻止当前线程,直到当前 WaitHandle 收到信号为止。 rEvent.WaitOne(Timeout.Infinite, true);
task
相对于线程池,task的可控制性就更高了,值得注意的是,task线程是异步执行的,也就是说task任务从作用上说是异步线程,从模式上说是属于多线程。这里只对task的用法做一个简单的介绍,想要深入理解,可以去百度一下专门讲解task的文章。
调用方法
private static void StartTask(object e) { Console.WriteLine("执行Task任务【{0}】", e); Thread.Sleep(1000); }
创建task任务
Console.WriteLine("创建Task任务"); new Task(StartCode, 1).Start(); Console.WriteLine("创建Task任务完成"); Thread.Sleep(1000);task任务可以用Cancel()方法取消,但是这是个异步请求,Task可能已经完成了。
再举一个计算的实例
简单的计算方法
private static Int Sum(Int i) { Int sum = 0; for (; i > 0; i--) checked { sum += i; } return sum; }
task任务创建与使用
Task<Int> t = new Task<Int>(i => Sum((Int)i), 10000000); t.Start(); //Wait显式的等待一个线程完成 t.Wait(); Console.WriteLine("计算结果:" + t.Result);
结果输出还可以这么写
Task cwt = t.ContinueWith(task=>Console.WriteLine("计算结果:{0}",task.Result)); cwt.Wait();
task创建调用的其他写法
//using task factory TaskFactory tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod); //using the task factory via a task Task t2 = Task.TaskFactory.StartNew(TaskMethod);
补充(Timer)
适用于需周期性调用的方法,它不在创建计时器的线程中运行,它在由系统自动分配的单独线程中运行。这和Win32中的SetTimer方法类似。它的构造为:
public Timer( TimerCallback callback,//所需调用的方法 object state,//传递给callback的参数 int dueTime,//多久后开始调用callback int period//调用此方法的时间间隔 ); // 如果 dueTime 为0,则 callback 立即执行它的首次调用。 // 如果 dueTime 为 Infinite,则 callback 不调用它的方法,计时器被禁用。 // 但使用 Change 方法可以重新启用它。 // 如果period 为0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用 // 但使用 Change 方法可以重新启用它。 // 如果 period 为零0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用。 // 但使用 Change 方法可以重新启用它。
改变它的period和dueTime,我们可以通过调用Timer的Change方法来改变:
public bool Change( int dueTime, int period );
调用写法
Timer tm=new Timer (new TimerCallback (CallBack),"Test",2000,2000); tm.Change (0,500);以上内容皆属个人对线程的理解,不一定正确,供参考学习,如有问题,欢迎指出交流。