多线程、任务、异步的区别

Task和Thread的区别

这是一个高频,深刻的问题,无论去哪都逃不过被询问这个问题。Task是基于Thread的,这是众所周知的。但是Task和Thread的联系如此简单和纯粹确实我没想到的。甚至只需要几十行代码就能呈现其原理。一个简单的模拟实例说明Task及其调度问题,这真是一篇好文章。

任务体系由两个类组成,Task,以及TaskScheduler

Task储存需要到多线程去执行的委托方法,尽管经过层层封装,内部最终还是调用这个委托。但是任务的执行方法不向程序员开放,而是交给了TaskScheduler,暴露给程序员的只有把任务交给任务调度器这个方法。任务说白了是围绕委托这个中心构建的。至于委托在哪个线程上执行,职责不在此,交给了任务调度器。

TaskScheduler用于决定将Task放到哪个线程上执行,最简单的是new Thread将Task及其内部的委托放进新线程去执行。复杂一点的就是调用线程池的排队方法,将Task放到线程池要访问的待执行Task队列中,让队列不断弹出Task,然后放到某个线程中去执行。

实现两个TaskScheduler

我举两个任务调度器来说明这有多简单。一个调度器用于对每个任务创建一个线程执行,另一个调度器用于创建一个线程池,并用线程池的线程去取任务执行。

  • 总是使用新线程执行任务
csharp 复制代码
//使用新线程执行任务
public class ThreadScheduler:TaskScheduler
{
	protected override void QueueTask(Task task)
	{
		//没想到就是直接把Task放到Thread中去执行了
		new Thread(()=>TryExecuteTask(task))
		.Start();
	}
}

//测试
ThreadScheduler threadScheduler = new ThreadScheduler();
Task.Factory.StartNew(() => Console.WriteLine($"Task1 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task2 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task3 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task4 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
  • 创建并使用线程池执行任务
csharp 复制代码
//使用线程池执行任务
public class ThreadPoolScheduler:TaskScheduler
{
	private BlockingCollection<Task> tasks=new();
	private Thread[] threads;
	
	//创建一个线程池,让线程不断去队列中取出任务执行
	public ThreadPoolScheduler(int threadNum)
	{
		threads=new Thread[threadNum];
		for(int i=0;i<threadNum;i++)
		{
			threads[i]=new Thread(InvokeNext);
			threads[i].Start();
		}
		void InvokeNext()
		{
			while(true)
			{
				var task=tasks.Take();
				if(task!=null)
				{
					TryExecuteTask(task);
				}
			}
		}
	}

	//新任务入队
	protected override void QueueTask(Task task)
	{
		tasks.Add(task);
	}
}


//测试
ThreadPoolScheduler threadScheduler = new ThreadPoolScheduler(2);
Task.Factory.StartNew(() => Console.WriteLine($"Task1 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task2 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task3 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);
Task.Factory.StartNew(() => Console.WriteLine($"Task4 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"), default, TaskCreationOptions.None, threadScheduler);

从这两种调度器可以看出,开始一个任务这个动作是唯一明确的只有一点,就是把任务交给调度器,而不是立即执行任务。至于任务有没有立即被放到线程中执行,这却决于任务调度器的实现。比如在第一种调度器中,任务被立即执行;在第二种调度器中,任务可能会等待,直到有空闲线程把它从队列中取出来。

回调

任务和多线程还有一个区别是拥有回调ContinueWith。这样就不需要使用阻塞或线程同步去解决这种很常见的,在一件事完成后再做另一件事的问题。大内老A 提出的方式是,在任务内部,在执行委托的那个函数中,在前一个委托执行完成后,开启一个新任务,执行下一个委托。

由于这个触发节点在前一个线程即将结束时,所以能实现回调。

由于回调和开始任务这两个方法有相同返回类型Task,所以又实现了链式调用。

异步

异步的解释是以同步的方式进行异步编程。这是对任务的进一步改进。这玩意只能通过语法糖去实现,达到的效果是将任务的回调执行模型变换为了直观上看起来的顺序执行模型。在多线程同步这个问题上,可以得出这样一条演变链条。

Thread Task async/await
锁,信号量等线程同步 回调 同步的方式编程

异步和多线程有什么区别?主要在于线程同步方式的区别吧。

相关推荐
慧都小妮子34 分钟前
基于.NET UA Client SDK构建跨平台OPC UA客户端应用
.net·opc ua·automation·跨平台应用·unified
追逐时光者1 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 58 期(2025年10.13-10.19)
后端·.net
CodeCraft Studio2 小时前
PDF处理控件Aspose.PDF教程:在C#中将PDF转换为Base64
服务器·pdf·c#·.net·aspose·aspose.pdf·pdf转base64
咕白m6255 小时前
C# 将多张图片转换到一个 PDF 文档
c#·.net
唐青枫8 小时前
C#.NET FluentValidation 全面解析:优雅实现对象验证
c#·.net
Aevget14 小时前
DevExpress WPF中文教程:Data Grid - 如何使用虚拟源?(二)
.net·wpf·界面控件·devexpress·ui开发·数据网格
从孑开始17 小时前
ManySpeech.MoonshineAsr 使用指南
人工智能·ai·c#·.net·私有化部署·语音识别·onnx·asr·moonshine
玩泥巴的1 天前
.NET驾驭Word之力:基于规则自动生成及排版Word文档
c#·word·.net·com互操作
专注VB编程开发20年1 天前
VB.NET多线程排序算法实现:LINQ与正则表达式方法
排序算法·.net·linq
EQ-雪梨蛋花汤1 天前
【踩坑记录】从“正在还原所需的工具包”说起:一次 .NET 工程包还原失败的完整排查实录
.net