重点是总结一些常用用法含义,个人体会他和委托密切相关,尤其是开启线程,初始化,
第一个知识点:线程使用
用法:new Thread(委托);
Thread线程开启,自定义方式解决线程执行完毕回调问题,达成Task.wait()的效果
Join()
官方有一句话:此方法更改调用线程的状态以包括 ThreadState.WaitSleepJoin。 不能对处于 ThreadState.Unstarted 状态的线程调用 Join。
不仅仅是只有这个方法,还有很多
thread1.Resume();//继续挂起的线程
thread1.Suspend();//挂起线程,暂停的意思
thread1.Abort();//终止线程
方法很多,但是实际上中看不中用,主要是线程本身是计算机的资源,不是程序凭空创造出来的数据对象,
造成很多的响应,不灵敏,可能申请停止,计算机操作系统并未及时停止,它要忙完自己的事情
这是.net frmwork 1.0提供的,存在这些问题
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program
{
static void Main(string[] args)
{
Func<int> func1 = () =>
{
return DateTime.Now.Year;
};
//Thread开启线程,但并没有提供控制线程执行顺序的Api,如何做呢?
//如果让某个线程
Thread thread1 = new Thread(() => {
Thread.Sleep(2000);
Console.WriteLine("我是线程1");
});
//thread1.Start();
//如何让线程1结束之后,才执行线程2
//**********第一个方式:while()
//while (thread1.ThreadState != ThreadState.Stopped)
//{
// Thread.Sleep(100);
//}
//如果开启线程没有停止,阻塞当前主线程,即不执行线程2
//这样带来一个问题,当前程序显示无响应状态,即卡顿状态
Thread thread2 = new Thread(() =>
{
//***********第二个方式:Join()
//阻塞线程1,查看官方文档即可知道它的作用
//直到它执行完成,如果没有执行完,将一直阻塞,里面可以传入一个时间参数
thread1.Join();
Console.WriteLine("我是线程2");
});
//thread2.Start();
//明显看出,即使线程1先开启,但是执行时间长,仍然是后执行输出,
//仅仅是通过代码先后顺序,无法真正执行控制顺序
//********第三种方式,两个委托,放在一个前台线程中执行
//如下需要两个委托,顺序执行,并且依赖第一个委托的结果,如下获取年份,
//调用方法,获取结果应该还是0,为啥,因为线程开启太慢,
//方法中线程还没来得及开启
//int res = CallReturn<int>(func1);
Func<int> funres = CallReturnFunc<int>(func1);
//这里实际上是让委托在子线程里执行,
int res = funres.Invoke();
//这里实际确保让子线程内容执行完成
}
//进一步如果要调用是带参数的返回呢?
//定义一个方法,基于传递一个委托参数,方法体里面将这个委托执行的值,作为参数,传递给开启线程执行另外一个委托,
//紧接着返回这个
private static T CallReturn<T>(Func<T> func)
{
T t = default(T);
//根据参数传进来的委托,执行完返回的值,进行接收
//开启新线程,执行新委托
//ThreadStart thread3 = new ThreadStart(()=>{
// t = func.Invoke();
//});
//Thread t3 = new Thread(thread3);
//t3.Start();
//一个多线程,像是异步的感觉
Thread t3 = new Thread(() =>
{
t = func.Invoke();
});
t3.Start();
return t;
}
//线程开启执行, 还没来得及执行完,主线程已经执行完毕,值未修改,优化如下:
//返回一个委托,里面强制要求他执行完,借助join方法,返回这个委托后,
//调用方法返回值委托的invoke方法即可
//方法后面定义的T,后面才能使用
private static Func<T> CallReturnFunc<T>(Func<T> func1) {
T t=default(T);
Thread thread = new Thread(() => {
t = func1.Invoke();
});
thread.Start();
//确保子线程执行完,返回一个委托,里面让join执行,
//同时在外面方法调用返回委托的Invoke方法
//return func1;
return new Func<T>(() => {
thread.Join();
return t;
});
}
//线程功能强大,但是使用起来并不友好,有点难使用,
//问题的关键在于线程数量是没有控制的,在系统资源可用的情况下,可一直使用,
//对于线程的开辟和销毁都是由程序员来控制,并且这个过程代价比较大
//正是由于这一个特性,提供一个新的方法,即线程池方式,
//线程池的思想,事先创建一部分的线程,放在线程中待用,
//要用直接取来用,用完还回来即可
}
}
这种原生的Thread方式,功能强大,不过掌握起来不太容易,很容易是得不偿失,原因是线程创建,关闭都是程序员操作,关键是平台没有办法限制,考虑线程创建销毁,极其占用资源,就像有一句说的,一个几岁小孩手持Ak步枪,使用不好,很有可能对自己造成伤害,另外一个思路应运而生,就是线程池,
简而言之是,不再是创建线程,而是从线程池中申请,取来用,用了还
第二个知识点:线程池,利用额外的ManualResetEvent事件,达成Task.wait()的效果
它实现顺序原理,基于flag标识,传进来一个标识对象MRE,一旦子线程里面完成,设置Set()一下
子线程方法里面,有一个参数,正好是MRE第二个参数,
外面通过MRE的一个方法WaitOne一直等待,等它执行完
使用语法很简单:.QueueUserWorkItem()里面接收一个委托,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPoolStart
{
class Program
{
//它实现顺序原理,基于flag标识,传进来一个标识对象MRE,一旦子线程里面完成,设置Set()一下
//子线程方法里面,有一个参数,正好是MRE第二个参数,
//外面通过MRE的一个方法WaitOne一直等待,等它执行完
//https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.waitcallback?view=netcore-3.1
/*WaitCallback 表示要在 ThreadPool 线程上执行的回调方法。 通过向 WaitCallback 构造函数传递回调方法来创建委托。 你的方法必须具有此处显示的签名。
通过将 WaitCallback 委托传递给 ThreadPool.QueueUserWorkItem来将方法排队以便执行。 当线程池线程变得可用时,回调方法会执行。*/
static void Main(string[] args)
{
//如果希望让线程池中线程等待,通过ManualResetEvent
ManualResetEvent mre = new ManualResetEvent(false);
//一直等待一个信号,有点监听的意思,等待谁呢?
//等待mre的ret,这个信号执行完成
//开启一个线程池,简单方法是直接传递一个方法
//默认需要符合WaitCallBack委托的方法,实际上传值给这个委托类型,无参带返回值
//第一种方式,直接一个带参无返回值的方法
//这个方法,有点像是Invoke执行一个委托,
//直接就是执行它
bool b1= ThreadPool.QueueUserWorkItem(TestThreadPool,mre);
//?????????传一个数据参数,反而不执行
//Thread.Sleep(1);
//new一个WaitCallBack委托
WaitCallback callback = new WaitCallback((o) => {
Console.WriteLine("我是委托实例中调用方法,传入数据:"+o.ToString());
});
//休眠1秒,仍然只有b2执行,因为b1还未执行完,随着主线程结束而结束
bool b2 = ThreadPool.QueueUserWorkItem(callback,"2");
//主线程休眠1ms,等待线程池中线程开始执行其中代码
//由于线程中线程都是后台线程,主线程关闭相对应子线程都结束
//Thread.Sleep(1000);
/*收到信号后,ManualResetEvent 会一直发出信号,直到通过调用 Reset() 方法进行手动重置。 也就是说,对 WaitOne 的调用会立即返回。*/
mre.WaitOne();
//这个方法同样是阻塞线程的
}
private static void TestThreadPool(object o){
ManualResetEvent mre=(ManualResetEvent)o;
Thread.Sleep(2000);
Console.WriteLine("线程中线程执行回调方法!方法参数:"+o.ToString());
mre.Set();
//这种情况,该如何传递这个信号
}
}
}
线程池,这种池化的思想,就是做一个容器,提前先申请需要的5个线程,程序需要直接从里面取,用完放回容器,线程池负责维护控制状态,当然有需要他也会再创建,销毁需要的线程,简单说帮我们维护线程,克服线程使用不当引起巨大的破坏,比如海量申请,造成死机等等的影响,不过他的API很少,使用起来极其不方便,并且顺序控制体验仍然不好,waitone,仍然是卡顿界面,阻塞主线程的
当然实际多线程使用,使用更加广泛的是Task,下一篇再讲,它使用起来更加方便快捷,不过它的实现原理,也是依赖于线程池,实际上C#语言,实现对线程的控制,主要就是这两种方式Thread和ThreadPool,其他方式都是进一步封装