vlambda博客
学习文章列表

.NET异步和多线程系列(二)- Thread和ThreadPool

一、Thread类

C#里面的多线程:Thread类是C#语言对线程对象的一个封装。

首先看下如何开启线程,执行委托的内容:

/// <summary>
/// 一个比较耗时耗资源的私有方法
/// </summary>
private void DoSomethingLong(string name)
{
Console.WriteLine($
"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = 0;
for (int i = 0; i < 1_000_000_000; i++)
{
lResult
+= i;
}
Thread.Sleep(
2000);
Console.WriteLine($
"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}

.NET异步和多线程系列(二)- Thread和ThreadPool

.NET异步和多线程系列(二)- Thread和ThreadPool

/// <summary>
/// 多线程 Thread类是.NET Framework 1.0的时候出现的
/// Thread:C#对线程对象的一个封装
/// </summary>
private void btnThread_Click(object sender, EventArgs e)
{
Console.WriteLine($
"****************btnThread_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

{
ParameterizedThreadStart method
= o => this.DoSomethingLong("btnThread_Click");
Thread thread
= new Thread(method);
thread.Start(
"浪子天涯");//开启线程,执行委托的内容
}

Console.WriteLine($
"****************btnThread_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

.NET异步和多线程系列(二)- Thread和ThreadPool

线程等待、线程优先级、前台线程和后台线程:

.NET异步和多线程系列(二)- Thread和ThreadPool

{
ThreadStart method
= () =>
{
Thread.Sleep(
5000);
this.DoSomethingLong("btnThread_Click");
Thread.Sleep(
5000);
};
Thread thread
= new Thread(method);
thread.Start();
//开启线程,执行委托的内容

//该花括号内的这些方法已经被微软抛弃了,建议不要去用
{
//thread.Suspend(); //暂停
//thread.Resume();//恢复 真的不该要的,暂停不一定马上暂停;让线程操作太复杂了
//thread.Abort();
//线程是计算机资源,程序想停下线程,只能向操作系统通知(线程抛异常),
//会有延时/不一定能真的停下来
//Thread.ResetAbort();
}


//1等待
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(
200); //当前线程休息200ms
}

//2 Join等待
thread.Join(); //运行这句代码的线程,等待thread的完成
thread.Join(1000); //最多等待1000ms

Console.WriteLine(
"这里是线程执行完之后才操作。。。");

//最高优先级:优先执行,但不代表优先完成看,甚至说极端情况下,还有意外发生,不能通过这个来控制线程的执行先后顺序
thread.Priority = ThreadPriority.Highest;

//是否是后台线程 默认是false
thread.IsBackground = false; //默认是false 前台线程,进程关闭,线程需要计算完后才退出
//thread.IsBackground = true;//关闭进程,线程退出
}

.NET异步和多线程系列(二)- Thread和ThreadPool

下面来看下Thread类的使用:

基于Thread封装一个带有回调的

.NET异步和多线程系列(二)- Thread和ThreadPool

/// <summary>
/// 基于Thread封装一个回调
/// 回调:启动子线程执行动作A--不阻塞--A执行完后子线程会执行动作B
/// </summary>
/// <param name="threadStart">多线程执行的操作</param>
/// <param name="actionCallback">线程完成后,回调的动作</param>
private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback)
{
//Thread thread = new Thread(threadStart);
//thread.Start();
//thread.Join(); //错了,因为方法被阻塞了
//actionCallback.Invoke();

ThreadStart method
= new ThreadStart(() =>
{
threadStart.Invoke();
actionCallback.Invoke();
});
new Thread(method).Start();
}

.NET异步和多线程系列(二)- Thread和ThreadPool

.NET异步和多线程系列(二)- Thread和ThreadPool

{
ThreadStart threadStart
= () => this.DoSomethingLong("btnThread_Click");
Action actionCallBack
= () =>
{
Thread.Sleep(
2000);
Console.WriteLine($
"This is Calllback {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
};
this.ThreadWithCallBack(threadStart, actionCallBack);
}

.NET异步和多线程系列(二)- Thread和ThreadPool

基于Thread封装一个带有返回值的

.NET异步和多线程系列(二)- Thread和ThreadPool

/// <summary>
/// 基于Thread封装一个带有返回值的
/// 1 异步,非阻塞的
/// 2 还能获取到最终计算结果
///
/// 既要不阻塞,又要计算结果?不可能!故此处返回一个委托,当外部需要使用结果的时候再阻塞,此时可能已经计算完了。
/// </summary>
private Func<T> ThreadWithReturn<T>(Func<T> func)
{
T t
= default(T);
ThreadStart threadStart
= new ThreadStart(() =>
{
t
= func.Invoke();
});
Thread thread
= new Thread(threadStart);
thread.Start();

return new Func<T>(() =>
{
thread.Join();
//thread.ThreadState
return t;
});
}

.NET异步和多线程系列(二)- Thread和ThreadPool

.NET异步和多线程系列(二)- Thread和ThreadPool

{
Func
<int> func = () =>
{
Thread.Sleep(
5000);
return DateTime.Now.Year;
};
Func
<int> funcThread = this.ThreadWithReturn(func);//非阻塞
Console.WriteLine("do something 1");
Console.WriteLine(
"do something 2");
Console.WriteLine(
"do something 3");
int iResult = funcThread.Invoke();//阻塞
}

.NET异步和多线程系列(二)- Thread和ThreadPool

控制线程的数量(仅供参考):

.NET异步和多线程系列(二)- Thread和ThreadPool

{
List
<Thread> threads = new List<Thread>();
for (int i = 0; i < 100; i++)
{
if (threads.Count(t => t.ThreadState == ThreadState.Running) < 10)
{
Thread thread
= new Thread(new ThreadStart(() => { }));
thread.Start();
threads.Add(thread);
}
else
{
Thread.Sleep(
200);
}
}
}

.NET异步和多线程系列(二)- Thread和ThreadPool

二、ThreadPool类

由于Thread类功能繁多,反而用不好--就像给4岁小孩一把热武器,反而会造成更大的伤害。而且对线程数量也是没有管控的。故微软在.NET Framework 2.0推出来ThreadPool线程池

如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子。

保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式)

节约资源提升性能;此外,还能管控总数量,防止滥用;

ThreadPool的线程都是后台线程。

下面我们直接来看下相关代码:

.NET异步和多线程系列(二)- Thread和ThreadPool

/// <summary>
/// ThreadPool线程池
/// 由于Thread类功能繁多,反而用不好--就像给4岁小孩一把热武器,反而会造成更大的伤害
/// 对线程数量是没有管控的
///
/// 线程池是.NET Framework 2.0推出来的
/// 如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子
/// 保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式)
/// 节约资源提升性能;此外,还能管控总数量,防止滥用;
///
/// ThreadPool的线程都是后台线程
///
/// 大家课后可以试试,基于ThreadPool去封装回调--返回值的
/// </summary>
private void btnThreadPool_Click(object sender, EventArgs e)
{
Console.WriteLine($
"****************btnThreadPool_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

//启动线程
{
ThreadPool.QueueUserWorkItem(o
=> this.DoSomethingLong("btnThreadPool_Click1"));
ThreadPool.QueueUserWorkItem(o
=> this.DoSomethingLong("btnThreadPool_Click2"), "浪子天涯");
}

{
ThreadPool.GetMaxThreads(
out int workerThreads, out int completionPortThreads);
Console.WriteLine($
"当前电脑最大workerThreads={workerThreads} 最大completionPortThreads={completionPortThreads}");

ThreadPool.GetMinThreads(
out int workerThreadsMin, out int completionPortThreadsMin);
Console.WriteLine($
"当前电脑最小workerThreads={workerThreadsMin} 最大completionPortThreads={completionPortThreadsMin}");

//设置的线程池数量是进程全局的(慎用,一般不用)
//委托异步调用--Task--Parrallel--async/await 全部都是线程池的线程
//直接new Thread不受这个数量限制的(但是会占用线程池的线程数量)
ThreadPool.SetMaxThreads(8, 8); //设置的最大值,必须大于CPU核数,否则设置无效
ThreadPool.SetMinThreads(2, 2);
Console.WriteLine(
"====================设置线程池数量最大最小====================");

ThreadPool.GetMaxThreads(
out int workerThreads1, out int completionPortThreads1);
Console.WriteLine($
"当前电脑最大workerThreads={workerThreads1} 最大completionPortThreads={completionPortThreads1}");

ThreadPool.GetMinThreads(
out int workerThreadsMin1, out int completionPortThreadsMin1);
Console.WriteLine($
"当前电脑最大workerThreads={workerThreadsMin1} 最大completionPortThreads={completionPortThreadsMin1}");
}

//线程等待
{
ManualResetEvent mre
= new ManualResetEvent(false);
//false---关闭---Set打开---true---WaitOne就能通过
//true---打开--ReSet关闭---false--WaitOne就只能等待
ThreadPool.QueueUserWorkItem(o =>
{
this.DoSomethingLong("btnThreadPool_Click1");
mre.Set();
});
Console.WriteLine(
"Do Something 1");
Console.WriteLine(
"Do Something 2");
Console.WriteLine(
"Do Something 3");

mre.WaitOne();
Console.WriteLine(
"任务已经完成了。。。");
}

//写多线程的时候有这么一种说法:不要阻塞线程池里面的线程。
//下面是一个死锁的例子
{
ThreadPool.SetMaxThreads(
8, 8);
ManualResetEvent mre
= new ManualResetEvent(false);
for (int i = 0; i < 10; i++)
{
int k = i; //此处必须声明一个变量存放i的值,不能直接使用i变量,否则会有问题
ThreadPool.QueueUserWorkItem(t =>
{
Console.WriteLine($
"{Thread.CurrentThread.ManagedThreadId.ToString("00")} show {k}");
if (k == 9) //设置了最多只允许8个线程,但此处是9,导致死锁了
{
mre.Set();
//开关打开
}
else
{
mre.WaitOne();
//线程等待,阻塞
}
});
}

if (mre.WaitOne()) //开关没打开,一直等待,死锁了
{
Console.WriteLine(
"任务全部执行成功!");
}
}

Console.WriteLine($
"****************btnThreadPool_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
$
"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

 

Demo源码:

链接:https://pan.baidu.com/s/1wVscaka37emNGz9x-rm0qA
提取码:3l2e

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13550714.html