Ответ 1
Есть действительно два этапа, отменяющих вопросы:
- Отмена первоначального выполнения запроса до возвращения первых строк
- Отмена процесса чтения строк по мере их обслуживания
В зависимости от характера фактического оператора 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);
}
Отмена блокировки вызовов для чтения()
Вид смешанного случая возникает, когда, как упоминалось ранее, обращение к 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";
}
Что касается производительности Reader vs Adapter
DataReader обычно быстрее, чем при использовании DataAdapter.Fill()
. Весь смысл DataReader - быть действительно, очень быстрым и отзывчивым для чтения. Проверка некоторого логического флага один раз за строку не добавит измеримой разницы во времени даже над миллионами строк.
Лимитирующим фактором для большого запроса базы данных является не время обработки локального процессора, а размер канала ввода-вывода (ваше сетевое соединение для удаленной базы данных или скорость вашего диска для локального) или комбинация db серверную скорость диска и время обработки процессора для сложного запроса. Оба DataAdapter и DataReader будут тратить время (возможно, большую часть времени), просто ожидая нескольких наносекунд за один раз для следующей строки, которая будет обслуживаться.
Одно удобство DataAdapter.Fill()
заключается в том, что он делает магию динамического создания столбцов DataTable для соответствия результатам запроса, но это не сложно сделать самому (см. SqlDataReader.GetSchemaTable()).