要在c#中用于繁重IO操作的线程类型



我的任务是更新一个操作中非常单线程的c#应用程序(非gui),并为其添加多线程,使其更快地转换工作队列。

每个线程将需要执行非常少量的计算,但大部分工作将调用并等待SQL Server请求。因此,与CPU时间相比,需要等待大量时间。

有几个要求是:

  • 在一些有限的硬件上运行(也就是说,只有几个内核)。当前的系统,当它是";"推";仅占用大约25%的CPU。但是,由于它主要是在等待SQL Server(不同的服务器)做出响应,因此我们希望能够拥有比核心更多的线程
  • 能够限制线程的数量。我也不能只拥有无限数量的线程。我不介意通过数组、列表等来限制自己
  • 能够跟踪这些线程何时完成,以便我可以进行一些后期处理

在我看来,.NET Framework有很多不同的线程处理方式,我不确定其中一种是否比另一种更好。我不确定我是否应该使用TaskThreadThreadPool或其他。。。据我所知,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有很多不同的线程处理方式,我不确定其中一种是否比另一种更好。

确实如此。作为参考,ThreadThreadPool现在几乎是遗留下来的;有更好的高级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);

就是这样。数据将自动从管道的第一个块流到最后一个块,并根据每个块的配置进行转换和处理。

这是一个有意简化的示例,用于演示基本功能。一个生产就绪的实现可能会定义更多的选项,如CancellationTokenBoundedCapacity,将观察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

最新更新