我的任务是更新一个操作中非常单线程的c#应用程序(非gui),并为其添加多线程,使其更快地转换工作队列。
每个线程将需要执行非常少量的计算,但大部分工作将调用并等待SQL Server请求。因此,与CPU时间相比,需要等待大量时间。
有几个要求是:
- 在一些有限的硬件上运行(也就是说,只有几个内核)。当前的系统,当它是";"推";仅占用大约25%的CPU。但是,由于它主要是在等待SQL Server(不同的服务器)做出响应,因此我们希望能够拥有比核心更多的线程
- 能够限制线程的数量。我也不能只拥有无限数量的线程。我不介意通过数组、列表等来限制自己
- 能够跟踪这些线程何时完成,以便我可以进行一些后期处理
在我看来,.NET Framework有很多不同的线程处理方式,我不确定其中一种是否比另一种更好。我不确定我是否应该使用Task
、Thread
、ThreadPool
或其他。。。据我所知,async
\await
模型在这种情况下并不适合,因为它需要等待一个特定的任务才能完成。
我不确定我是否应该使用Task、Thread、ThreadPool等。。。
在你的情况下,这比你想象的要重要。您可以专注于最适合您(现有)的代码样式和数据流。
因为它主要是在等待SQL Server响应
您的主要目标是让尽可能多的SQL查询并行运行。
能够限制线程数。
不要太担心。在4核上,使用25%的CPU,您可以轻松地拥有100个线程。更多关于64位的信息。但是你不想要1000个线程。一个.net线程至少使用1MB,估计你能腾出多少RAM。
因此,这取决于您的应用程序,同时可以运行多少查询。首先要担心螺纹安全。
当并行查询的数量>1000,则需要async/await才能在更少的线程上运行。
只要是<100,只需让线程阻塞I/O。CCD_ 6、CCD_。
100-1000的范围是灰色区域。
为其添加多线程,使其能够更快地转换工作队列。
每个线程将需要执行非常少量的计算,但大部分工作将调用并等待SQL Server请求。因此,与CPU时间相比,需要等待大量时间。
有了这种处理,还不清楚多线程会给您带来什么好处。多线程是一种形式的并发,由于您的工作负载主要是I/O绑定的,异步(而不是多线程)将是第一个需要考虑的问题。
在我看来,.NET Framework有很多不同的线程处理方式,我不确定其中一种是否比另一种更好。
确实如此。作为参考,Thread
和ThreadPool
现在几乎是遗留下来的;有更好的高级API。如果用作委托任务(例如Task.Factory.StartNew
),则Task
也应该是罕见的。
我觉得异步\await模型在这种情况下并不适合,因为它等待一个特定的任务来完成。
await
将一次等待一个任务,是的。Task.WhenAll
可用于组合多个任务,然后您可以对组合的任务执行await
。
让它更快地完成工作队列。
能够限制线程的数量。
能够跟踪这些线程何时完成,以便我可以进行一些后期处理。
在我看来,TPL数据流将是您的系统的最佳方法。数据流允许您定义一个";管道";其中一些步骤是异步的(例如查询SQL Server),而其他步骤是并行的(例如数据处理)。
我问了一个高级问题,试图得到一个高级答案。
你可能对我的书感兴趣。
TPL数据流库可能是此工作的最佳选择之一。以下是如何构建一个由两个块组成的简单数据流管道。第一个块接受一个文件路径并生成一些中间数据,这些数据稍后可以插入数据库。第二个块通过将来自第一个块的数据发送到数据库来消耗这些数据。
var inputBlock = new TransformBlock<string, IntermediateData>(filePath =>
{
return GetIntermediateDataFromFilePath(filePath);
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = Environment.ProcessorCount // What the local machine can handle
});
var databaseBlock = new ActionBlock<IntermediateData>(item =>
{
SaveItemToDatabase(item);
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 20 // What the database server can handle
});
inputBlock.LinkTo(databaseBlock);
现在,每次用户上传文件时,只需将文件保存在临时路径中,并将路径发布到第一个块:
inputBlock.Post(filePath);
就是这样。数据将自动从管道的第一个块流到最后一个块,并根据每个块的配置进行转换和处理。
这是一个有意简化的示例,用于演示基本功能。一个生产就绪的实现可能会定义更多的选项,如CancellationToken
和BoundedCapacity
,将观察inputBlock.Post
的返回值,以在块不能接受作业的情况下做出反应,可能有完成传播,观察databaseBlock.
Completion
属性的错误等。
如果你对遵循这条路线感兴趣,最好研究一下图书馆,以便熟悉可用的选项。例如,有一个TransformManyBlock
可用,适用于从单个输入产生多个输出。CCD_ 21在某些情况下也可能是有用的。
TPL数据流内置于。NET Core,并作为的包提供。NET框架。它有一些学习曲线,也有一些需要注意的问题,但这并不可怕。
我觉得异步\await模型在这种情况下并不适合,因为它等待一个特定的任务来完成。
这是错误的。Async/await只是一种语法,用于简化异步代码的状态机机制。它等待而不消耗任何线程。换句话说,CCD_ 22关键字不创建线程并且CCD_。
能够限制线程的数量
请参阅如何限制并发异步I/O操作的数量?
能够跟踪这些线程何时完成,以便我可以进行一些后期处理。
如果您不使用";火与遗忘;然后您可以通过编写await task
来跟踪任务及其异常
var task = MethodAsync();
await task;
PostProcessing();
async Task MethodAsync(){ ... }
或者,对于类似的方法,您可以使用ContinueWith
:
var task = MethodAsync();
await task.ContinueWith(() => PostProcessing());
async Task MethodAsync(){ ... }
阅读更多:
在异步任务期间释放线程
https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/?redirectedfrom=MSDN