(十八)Threads异步和多线程(Thread、Threadpool、Task)-语言进阶2

本文详细介绍了多线程编程中的关键概念,包括Thread的启动、等待、前台/后台线程以及线程回调。接着讨论了线程池(ThreadPool)的使用和线程限制,展示了ManualResetEvent在等待线程池任务时的作用。然后转向Task的启动方式,如Run和TaskFactory,以及WaitAll和WaitAny的使用场景。此外,还探讨了如何控制并行运算的并发量,并介绍了Parallel类的应用。文章提供了丰富的示例代码,帮助理解各种异步和并行处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Thread

1. 线程启动

thread.Start();

ThreadStart threadStart = () => this.DoSomethingLong("btnThreads_Click")Thread thread = new Thread(threadStart);
thread.Start() ;

2. 线程等待

  1. 当线程启动之后,我们需要线程等待时,可以用
    thread.Join();//一直等待任务完成
    thread.Join(500); //设置最多等待500ms
  2. Sleep方式
while (thread.ThreadState != ThreadState.Stopped)
{
	Thread.Sleep(100);//当前线程 休息100ms
}

3.前台线程/后台线程

  1. thread.IsBackground默认前台线程,启动之后一定会完成任务,阻止进程退出。
  2. thread.IsBackground=true;设置后台线程,随着进程退出
  3. Thread.Priority;//设置线程优先级,其中CPU会优先执行Highest,不代表说Highest最先完成。
    在这里插入图片描述

4.扩展thread封装回调

  1. thread封装回调
//启动子线程计算--完成委托后,该线程去执行后续回调委托
private void ThreadWithCallback(Action act, Action callback)
{
	Thread thread = new Thread(() =>
	{
		act.Invoke();
		callback.Invoke():
	});
	thread.Start():
}
  1. 带返回的异步调用 需要获取返回值
private Func<T ThreadWithReturn<I>(Func<T> func)
{
	T t = default(T);
	Thread thread = new Thread(()=>
	{
		t = func.Invoke();
	});
	thread.Start() ;
	//返回Func<T>
	return ()=>
	{
		while (thread.ThreadState != ThreadState.Stopped)
		{
			Thread.Sleep(200);
		}
	}
	return t;
}

其中while判断处也可以换成 thread.Join();
调用方式
在这里插入图片描述

二、Threadpool

1.线程池

  1. 线程池:线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
  2. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  3. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  4. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.线程池使用

  1. 线程池启用
    ThreadPool.QueueUserWorkItem();//接受一个WaitCallback类型的参数;
    WaitCallback:没返回值的委托;
    在这里插入图片描述
  2. 线程限制:最大线程
ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortlhreads();

GetMaxThreads:检索可以同时处于活动状态的线程池请求的数目。 所有大于此数目的请求将保持排队状态
workerThreads:线程池重辅助线程的最大数目。
completionPortlhreads:线程池中异步I/O线程的最大数目
3. 线程限制:最小线程

ThreadPool.GetMinThreads(out int workerThreads, out int completionPortlhreads();

3.ManualResetEvent 线程池等待

//ManualResetEvent 类 包含了一个bool属性
//false–WaitOne等待–Set–true–WaitOne直接过去
//true–WaitOne直接过去–reset–false–WaitOne等待

ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(t =>
{
	this.DoSomethingLong("btnThreadPool Click");
	manualResetEvent.Set() :
	//manualResetEvent.Reset() 
});
manualResetEvent.WaitOne ();
Console.WriteLine("等着QueueUserWorkItem完成后才执行");

但不建议这么用,因为一般来说,不要阻塞线程池的线程。

三、Task

Task是基于ThreadPool,增加了多个API

1. Task启动方式

  1. Run
Task.Run(()=>this.DoSomethingLong("btnTask_Click1"));
  1. TaskFactory
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(() => this.DoSomethingLong("btnTask Click3")):
new Task(0 => this.DoSomethingLong("btnTask Click4")).Start():

2.waitall 、waitany

1.waitall

waitall:阻塞当前线程,等着任务全部完成,才进入下一行,不过会卡界面
当我们在多线程中,需要在所有任务都完成后,给与正确提示,如下例子,当所有开发任务完成,告诉甲方验收

在这里插入图片描述
可以看下这里的运行结果,实际开发任务并没有完成,但却告诉已经完成,不符合需求在这里插入图片描述
这时我们就可以使用waitall阻塞当前线程,等完成后提示。
在这里插入图片描述
也可以传入指定时间,限时等待。

Task.WaitAll(taskList.ToArray()1000);//限时等待

2.waitany

waitany:会阻塞当前线程,等着某个任务完成后,才进入下一行 卡界面

Task.WaitAny(taskList.ToArray());
Task.WaitAny(taskList.ToArray(),1000);

如果想做到 waitAll和waitAny不卡界面,则可以在包一层task,因为其中卡的是运行线程,包一层则意味着又是一个新的线程在执行
在这里插入图片描述

3.WaitAll、waitany场景

  1. WaitAll:一个业务查询操作有多个数据源——首页——多线程并发——拿到全部数据后才能返回
  2. WaitAny:一个商品搜索操作有多个数据源,商品搜索——多个数据源——多线程并发——只需要一个结果即可

4. 应用:线程数量控制

List<int> list = new List<int>();
for (int i = 0;i < 10000; i++){
	list.Add(i);
}

//完成1000个任务,但只需要11个线程
Actlon<int>action = i =>
{
	Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"));
	Thread.Sleep(new Random(i).Next(100300));
};
List<Task> taskList = new List<Task>();
foreach (var i in list)
{
	int k = i;
	taskList.Add(Task.Run(()=> action.Invoke(k)));
	if (taskList.Count > 10)
	{
		//等待某个任务完成
		Task.WaitAny(taskList.ToArray());
		//未完成则保留
		taskList = taskList.Where(t => t. Status != TaskStatus.RanToCompletion).Tolist());
	}
}
Task.WhenA11(taskList.ToArray());

5.扩展:获取哪个任务先完成

  1. 使用TaskFactory在任务中追加标识,使用AsyncState获取任务
    在这里插入图片描述
  2. 如果是Task的话 可以做个子类做属性标识

3. WhenAny、WhenAll

上述我们发现WaitAll、waitany一般情况会卡界面,那么接下来我们来解决这个情况
WhenAny、WhenAll:创建在task完成时,异步执行的延续任务,则不会卡界面,如下
在这里插入图片描述
在这里插入图片描述

4.ContinueWherAny、ContinueWhenAll

ContinueWherAny、ContinueWhenAll则是TaskFactory中使用,效果和WhenAny、WhenAll一样
在这里插入图片描述

5.Delay() 延迟

Task.Delay(1000);延迟 不会卡
Thread.Sleep(1000);等待,会卡

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start0():
Task.Delay(2000).ContinueWith(t =>
{
	stopwatch.Stop () ;
	Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

如果想sleep也不卡的话,仍是包一层task开启个新线程

Stopwateh stopwatch = new Stopwatch();
stopwatch.Start();
Task.Run(()=>
{
	Thread.sleep(2000)stopwatch.Stop();
	Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

四、并行运算Parallel

Parallel:并行编程,在Task的基础上做了封装,卡界面,因为主线程参与计算,节约了一个线程

1. Parallel启动方式

  1. Invole
Parallel.Invoke(() => this.Coding("爱书客""Client"),
	()=> this.Coding("风动寂野","Portal"),
	()=> this.Coding("笑看风云","Service"));
  1. For
//启动5个线程
Parallel.For(0,5,i =>Coding("爱书客","Client"+i));
  1. Foreach
Parallel.ForEach(new string[] { "0","1","2","3","4"),i => this.Coding("爱书客","client"+1));

2. Parallel限制线程数量

1.控制并发量

使用ParallelOptions 的MaxDegree0fParallelism 限制线程数量

ParallelOptions parallelOptions = new ParallelOptions();
parallel0ptions.MaxDegree0fParallelism = 3;//限制3个线程
Parallel.For(0,10,parallelOptions,i =>this.Coding("爱书客","Client"+ i));

在这里插入图片描述
从运行结果可以看到任意时刻只有三个线程在运行。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值