10

我试图使标题尽可能具体。基本上我现在在后台工作线程中运行的是一些看起来像这样的代码:

 SqlConnection conn = new SqlConnection(connstring);
                    SqlCommand cmd = new SqlCommand(query, conn);
                    conn.Open();
                    SqlDataAdapter sda = new SqlDataAdapter(cmd);
                    sda.Fill(Results);
                    conn.Close();
                    sda.Dispose();

其中 query 是一个字符串,表示一个大的、耗时的查询,conn 是连接对象。

我现在的问题是我需要一个停止按钮。我开始意识到杀死后台工作人员将毫无价值,因为我仍然想保留取消查询后剩余的结果。另外,直到查询之后,它才能检查取消状态。

到目前为止我想出了什么:

我一直在尝试概念化如何有效地处理这个问题而不会对性能造成太大影响。

我的想法是使用 SqlDataReader 一次从查询片段中读取数据,以便我有一个“循环”来检查我可以通过按钮从 GUI 设置的标志。问题是据我所知,我不能使用数据表的 Load() 方法,但仍然能够取消 sql 命令。如果我错了,请告诉我,因为这会使取消稍微容易一些。

根据我的发现,我意识到如果我执行以下操作(伪代码),我可能只能取消 sqlcommand 中间查询:

while(reader.Read())
{
 //check flag status
 //if it is set to 'kill' fire off the kill thread

 //otherwise populate the datatable with what was read
}

然而,在我看来,这将是非常无效的并且可能代价高昂。这是杀死绝对需要在数据表中的 sqlcommand 的唯一方法吗?任何帮助,将不胜感激!

4

2 回答 2

5

实际上有两个阶段取消很重要:

  1. 在返回第一行之前取消初始查询执行
  2. 在提供行时中止读取行的过程

根据实际 sql 语句的性质,这两个步骤中的任何一个都可能有 99% 的时间,因此应该考虑它们。例如,调用SELECT *具有十亿行的表基本上不会花费时间来执行,但会花费很长时间读取。相反,在调整不佳的表上请求超级复杂的连接,然后将其全部包装在一些聚合子句中可能需要几分钟来执行,但一旦实际返回少数行,读取这些行的时间可以忽略不计。

经过良好调整的高级数据库引擎还将一次缓存多块行以进行复杂查询,因此您将看到交替的暂停,引擎在下一批行上执行查询,然后在返回下一批时快速爆发数据结果。

取消查询执行

为了能够在查询执行时取消查询,您可以使用SqlCommand.BeginExecuteReader的重载之一来启动查询,并调用SqlCommand.Cancel来中止它。或者,您可以在一个线程中同步调用 ExecuteReader(),但仍从另一个线程调用 Cancel()。我不包括代码示例,因为文档中有很多。

中止读取操作

这里使用一个简单的布尔标志可能是最简单的方法。请记住,使用带有对象数组的 Rows.Add() 重载来填充数据表行非常容易,即:

object[] buffer = new object[reader.FieldCount]
while(reader.Read()) {
    if(cancelFlag) break;
    reader.GetValues(buffer);
    dataTable.Rows.Add(buffer);
}

取消对 Read() 的阻塞调用

如前所述,当调用 reader.Read() 导致数据库引擎执行另一批密集处理时,就会出现一种混合情况。如 MSDN 文档中所述,Read()在这种情况下,即使原始查询是使用BeginExecuteReader. 你仍然可以通过调用Read()一个处理所有读取但调用Cancel()另一个线程的线程来解决这个问题。您知道您的阅读器是否处于阻塞Read调用中的方法是在监视线程读取时让阅读器线程更新另一个标志:

...
inRead = true
while(reader.Read()) {
    inRead = false
    ...
    inRead = true
}

// Somewhere else:
private void foo_onUITimerTick(...) {
   status.Text = inRead ? "Waiting for server" : "Reading";
}

关于阅读器与适配器的性能

DataReader 通常比使用DataAdapter.Fill(). DataReader 的全部意义在于非常非常快速地响应阅读。即使超过数百万行,每行检查一次布尔标志也不会增加可测量的时间差异。

大型数据库查询的限制因素不是本地 CPU 处理时间,而是 I/O 管道的大小(远程数据库的网络连接或本地数据库的磁盘速度)或 db 服务器自己的磁盘的组合复杂查询的速度和 CPU 处理时间。DataAdapter 和 DataReader 都将花费时间(可能是大部分时间)一次只等待几纳秒,以便为下一行提供服务。

一个方便之DataAdapter.Fill()处在于它具有动态生成 DataTable 列以匹配查询结果的魔力,但这并不难做到(参见SqlDataReader.GetSchemaTable())。

于 2012-06-28T13:16:38.947 回答
0

只是一试

我建议您在BackgroundWorker中放置一个耗时的查询并将命令传递给它。这样您就可以控制命令对象。当取消命令到来时,只需说通过(到正在进行的BackgroundWorker)命令取消command.Cancel()

于 2012-06-28T09:14:23.577 回答