打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
委托与线程的见解(下)——线程

线程相关

        关于线程的概念很多,简单的说,线程是程序执行流的最小单元,如果把进程比作一条河流,那么线程就是河流的一条小支流。他是独立执行,却可能对主进程有影响。

常识

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
版权声明:本文为博主原创文章,转载请附上博文链接!

来源:http://www.icode9.com/content-4-71351.html
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#多线程编程(二)线程池与TPL
C#线程学习笔记七:Task详细用法
详解.NET异步
探索c#之Async、Await剖析
线程(一)——线程,线程池,Task概念+代码实践
值得一看:C#同步方法中如何调用异步方法?
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服