委托与线程的见解(下)——线程
线程相关
关于线程的概念很多,简单的说,线程是程序执行流的最小单元,如果把进程比作一条河流,那么线程就是河流的一条小支流。他是独立执行,却可能对主进程有影响。
常识
1. 前台线程和后台线程:通过Thread类新建线程 thread1 默认为前台线程。当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常。将前台线程转后台线程,只需thread1.IsBackground = true
2. 挂起(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);
注意:
线程调用执行的程序,如果对界面 UI 有做操作,会造成一定的界面异常现象,这时候就需要使用到委托,将需要对界面UI操作的内容放到委托去执行,这操作可叫做卸载委托。这里的委托可以有两种,一种是控件异步委托,一种是同步委托,写法如下:
异步
this.BeginInvoke((EventHandler)delegate
{
//UI操作
});
同步
this.Invoke((EventHandler)delegate
{
//UI操作
});
---------------------
作者:玲妹妹的辉哥哥
来源:CSDN
原文:https://blog.csdn.net/qwerdfcv/article/details/80292840
版权声明:本文为博主原创文章,转载请附上博文链接!