【发布时间】:2018-08-29 13:35:00
【问题描述】:
我正在将一系列 select 语句(查询 - 数以千计)同步提交到单个数据库,并在每个查询中返回一个 DataTable(注意:该程序具有数据库模式的知识它仅在运行时扫描,因此使用DataTables)。该程序在客户端机器上运行并连接到远程机器上的数据库。运行这么多查询需要很长时间。因此,假设异步或并行执行它们会加快速度,我正在探索TPL Dataflow (TDF)。我想使用TDF 库,因为它似乎可以处理与编写多线程代码相关的所有问题,否则这些问题需要手动完成。
显示的代码基于http://blog.i3arnon.com/2016/05/23/tpl-dataflow/。它是最小的,只是为了帮助我理解TDF的基本操作。请知道我已经阅读了许多博客并编写了许多迭代来试图破解这个坚果。
不过,在当前的迭代中,我有一个问题和一个问题:
问题
代码位于button click 方法中(使用 UI,用户选择一台机器、一个 sql 实例和一个数据库,然后开始扫描)。带有await 运算符的两行在构建时返回错误:The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'。我无法更改按钮单击方法的返回类型。我是否需要以某种方式将button click 方法与async-await 代码隔离开来?
问题
虽然我找到了描述TDF 基础知识的漂亮文章,但我找不到一个示例来说明如何掌握每次调用TransformBlock 产生的输出(即, DataTable)。虽然我想提交查询async,但我确实需要阻止,直到提交给TransformBlock 的所有查询都完成。如何获得由TransformBlock 生成的一系列DataTables 并阻止直到所有查询完成?
注意:我承认我现在只有一个街区。至少,我将添加一个取消块,因此需要/想要使用 TPL。
private async Task ToolStripButtonStart_Click(object sender, EventArgs e)
{
UserInput userInput = new UserInput
{
MachineName = "gat-admin",
InstanceName = "",
DbName = "AdventureWorks2014",
};
DataAccessLayer dataAccessLayer = new DataAccessLayer(userInput.MachineName, userInput.InstanceName);
//CreateTableQueryList gets a list of all tables from the DB and returns a list of
// select statements, one per table, e.g., SELECT * from [schemaname].[tablename]
IList<String> tableQueryList = CreateTableQueryList(userInput);
// Define a block that accepts a select statement and returns a DataTable of results
// where each returned record is: schemaname + tablename + columnname + column datatype + field data
// e.g., if the select query returns one record with 5 columns, then a datatable with 5
// records (one per field) will come back
var transformBlock_SubmitTableQuery = new TransformBlock<String, Task<DataTable>>(
async tableQuery => await dataAccessLayer._SubmitSelectStatement(tableQuery),
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 2,
});
// Add items to the block and start processing
foreach (String tableQuery in tableQueryList)
{
await transformBlock_SubmitTableQuery.SendAsync(tableQuery);
}
// Enable the Cancel button and disable the Start button.
toolStripButtonStart.Enabled = false;
toolStripButtonStop.Enabled = true;
//shut down the block (no more inputs or outputs)
transformBlock_SubmitTableQuery.Complete();
//await the completion of the task that procduces the output DataTable
await transformBlock_SubmitTableQuery.Completion;
}
public async Task<DataTable> _SubmitSelectStatement(string queryString )
{
try
{
.
.
await Task.Run(() => sqlDataAdapter.Fill(dt));
// process dt into the output DataTable I need
return outputDt;
}
catch
{
throw;
}
}
【问题讨论】:
-
错误的假设。如果查询很慢修复它。在同一网络上使用同一 CPU 和同一磁盘的同一服务器上运行更慢的查询只会慢 事情。将结果加载到 DataTable 会增加更多延迟。
-
将所有内容加载到 client 进行处理的查询会导致更严重的延迟。 client 比服务器具有更少的内存、更少的 CPU、更少的磁盘 IO 和 no 索引以加快速度。将所有内容加载到客户端进行处理是个坏主意。
-
顺便说一句,如果您尝试处理数据以生成报告或填充报告模式,请创建适当的报告数据库或数据仓库并使用 ETL 工具(如 SSIS)填充它。仅更新已更改的行。查询性能将比在客户端处理好很多数量级。仅处理更改将比拉动所有内容快几个数量级。
-
@PanagiotisKanavos:所有好的和有效的点。然而,事实就是如此。要求使用该程序不涉及对正在扫描其数据库的(生产)机器进行任何更改。我只需要尽可能快地运行查询,而不一定非常快
标签: c# task-parallel-library tpl-dataflow