2010-11-08 1 views
0

Прямо сейчас у меня есть функция, которая выполняет запрос, используя ADO и возвращает recordset:ADO: Как выполнить запрос синхронно с возможностью отмены?

Recordset Execute(Connection connection, String commandText) 
{ 
    //[pseudo-code] 
    Recordset rs = new Recordset(); 
    rs.CursorLocation = adUseClient; 
    rs.CursorType = adOpenForwardOnly; 
    rs.Open(commandText, connection, 
     adOpenForwardOnly, //CursorType; the default 
     adLockReadOnly, //LockType 
     adCmdText);  

    return rs; 
} 

И это прекрасно. Он работает синхронно и возвращает набор записей запроса.

Теперь я хочу такой же вариант, что показывает ProgressDialog, предоставляя пользователю возможность отменить давний запрос:

Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText) 
{ 
    //[pseudo-code] 

    //Construct a progressDialog and show it 
    IProgressDialog pd = new ProgressDialog(); 
    pd.SetTitle(caption); //e.g. "Annual Funding Report" 
    pd.SetCancelMsg("Please wait while the operation is cancelled"); 
    pd.StartProgressDialog(parenthWnd, null, PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOMINIMIZE, null); 
    pd.SetLine(1, "Querying server", False, null); 
    try 
    { 
     //Query the server 
     Recordset rs = new Recordset(); 
     rs.Open(commandText, connection, 
      adOpenForwardOnly, //CursorType 
      adLockReadOnly, //LockType 
      adCmdText | adAsyncExecute); 

     while (rs.State and (adStateConnecting+adStateExecuting+adStateFetching) <> 0) 
     { 
     if pd.HasUserCancelled() 
      throw new EUserCancelledOperationException(); 

     Sleep(100); 
     }; 

    finally 
    { 
     //Hide and destroy the progress dialog  
     pd.StopProgressDialog(); 
     pd = null; 
    } 

    //Now we have our results for the client 
    return rs; 
} 

Для проверки того, если пользователь отменил операция периодически спрашивают диалог выполнения, если пользователь, как нажал кнопку отмены:

pd.HasUserCancelled(); //returns true if user has clicked Cancel 

Теперь я сталкиваюсь с тем, как периодически проверять, если пользователь отменил (d знать, завершен ли запрос или произошла ошибка), и быть хорошим программистом и делать это без опроса.

Единственный способ узнать, что случилась ошибка, чтобы иметь обработчик на FetchCompleteEvent набора записей:

pError
Объект Error. Он описывает ошибку, которая произошла, если значение adStatus - adStatusErrorsOccurred; иначе он не будет установлен.

adStatus
Значение статуса EventStatusEnum. Когда это событие вызывается, этот параметр устанавливается в adStatusOK, если операция, вызвавшая событие, была успешной, или adStatusErrorsOccurred, если операция завершилась неудачно.

Прежде чем это событие вернется, установите этот параметр в adStatusUnwantedEvent, чтобы предотвратить последующие уведомления.

pRecordset
Объект Recordset. Объект, для которого были получены записи.

Так что это означало бы, что мне нужно будет построить мою функцию вспомогательного объекта, поэтому у меня может быть обработчик FetchComplete. Но тогда я должен предотвратить немедленное возвращение функции синхронной функции. И затем мы попадаем в MsgWaitForSingleObject, что, как известно, сложно использовать правильно.

Так что я прошу помощи или законсервированного кода.


я должен быть более четко: я ищу для реализации функции с помощью этого метода подписи:

Recordset ExecuteWithCancelOption(Connection connection, String commandText) 

, который показывает диалоговое окно с помощью кнопки отмены на нем.

Задача состоит в том, что функция должна теперь создавать все, что требуется для достижения этого. Если это связано с скрытой формой, на которой есть таймер, и т. Д. - хорошо.

Но я ищу для синхронного функции, которая отображает Отмену кнопку.

И функция будет рядом (или точнее) вставных замены для

Recordset Execute(Connection connection, String commandText) 

С учетом практических соображений, на Windows, я должен был бы поставить функцию с родительского окна ручкой, что он будет родитель его диалог с:

Recordset ExecuteWithCancelOption(HWND parentHwnd, Connection connection, String commandText) 

И учитывая, что это будет многоразовая функция, я дам звонящему предоставить текст, который будет отображаться:

Recordset ExecuteWithCancelOption(HWND parenthWnd, String caption, Connection connection, String commandText) 

И учитывая, что эти обе функции класса в моем TADOHelper классе, я могу дать им такое же имя, и у них будет перегруженные друг друга:

Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText) 

Я думаю, в других чем Delphi языках, анонимные делегаты полезны. Но я все еще боюсь иметь дело с MsgWaitForMultipleObjects.

ответ

0

Если это Delphi, вы можете удалить компонент TTimer и использовать его, чтобы проверить, имеет ли значение HasUserCancelled значение True. У меня нет Delphi передо мной, поэтому мне придется опубликовать пример позже.

Edit:

Вот пример события TTimer OnTimer, который проверяет текущее время и время lastactivity, чтобы решить, что делать с формами, если программа была оставлена ​​«Up»:

procedure TForm_Main.Timer1Timer(Sender: TObject); 
begin 
    // return to opening screen if no activity for a while: 
    if Now - LastActivity > TimeOut 
    then 
    begin 
     Form_Select.SBtn_Defendant.Down:= False; 
     Form_Select.SBtn_Officer.Down:= False; 
     Form_Select.SBtn_Attorney.Down:= False; 
     Form_Main.ModalResult:= mrCancel; 
     Exit; 
    end; 
    Form_Main.Caption:= FormatDateTime('dddd mmmm d, yyyy h:nn:ss AM/PM', Now); 
end; 
+0

Таймер будет таким же, как «Сон». Он не фиксирует реальную проблему при опросе, и я не могу получить доступ к аргументу «Ошибка». –

+0

Таймер - это не то же самое, что Sleep. Сон: приостанавливает выполнение программы за указанное количество микросекунд. Таймер может проверять каждую секунду, чтобы проверить, выполнено ли какое-либо другое условие. – Leslie

1
  1. Информация о проделанной работе и изящная отмена запроса могут быть недоступны в каждом движке базы данных. Им нужна поддержка базы данных, как на сервере, так и на стороне клиента. Например, Oracle позволяет отменить запрос, но не имеет информации о «прогрессе», но просматривает представление V $ SESSION_LONGOPS. Конечно, вы можете убить сеанс, но он отбросит все это, а не просто отменит выполнение запроса запроса.
  2. Обычно, если база данных поддерживает этот вид функций, запрос запускается в отдельном потоке, который будет ждать результата. Таким образом, основной поток может по-прежнему получать пользовательский ввод, считывать и отображать информацию о ходе работы (если не возвращаться в какой-либо обратный вызов). Если пользователь отменяет запрос, то соответствующий вызов выдается для остановки операции, позволяя потоку запроса возвращаться, обычно поток получает код состояния, который будет сообщать, что произошло.
  3. Помнит о том, как ADO реализует асинхронные операции: http://msdn.microsoft.com/en-us/library/ms681467(VS.85).aspx
  4. Там также FetchProgress() событие, которое может помочь вам, если вы не хотите идти нить пути (и даже тогда отменить запрос, если это возможно)
+0

Я не могу запустить запрос в отдельном потоке. Правила COM запрещают объекту получать доступ к квартире, отличной от той, которая ее создала. К счастью, ADO имеет асинхронные операции, поэтому отдельный поток не требуется. –

+0

Вы можете - даже с COM (http://support.microsoft.com/kb/206076). Но вам решать, как закодировать ваше приложение. – 2010-11-10 08:06:22

+0

Да, вы можете. Вы должны вызвать CoInitialize в потоке перед созданием COM-объекта. – Linas

1

Чтобы заставить графический интерфейс реагировать на нажатия кнопок, вы должны вернуть управление контуру сообщения в окне. Пока цикл while (rs.State и (adStateConnecting + adStateExecuting + adStateFetching) <> 0) не возвращает управление обратно в цикл сообщений, тем самым блокируя GUI.

Ниже приведен фрагмент рабочего кода Delphi, в котором используются асинхронные запросы ADO. Этот код не допускает немодальную выборку данных, но гарантирует, что основная форма будет перерисована во время извлечения данных, а также позволит отменить запрос.

Асинхронное выполнение и выборка достигается путем установки:
FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking]; исполнение запроса отменяется путем вызова
DataSet.Recordset.Cancel;
в FetchProgress случае.

Любой TADODataSet должен быть открыт с помощью метода:

OpenDataSetInBackground(DataSourceData.DataSet as TADODataSet); 

Поддерживая код в главной форме: форма

procedure TOperatorForm.OpenDataSetInBackground(DataSet: TADODataSet); 
begin 
    if DataSet.Active then Exit; 
    FOpeningDataSet := DataSet; 

    if not FAsyncDataFetch then 
    begin 
    FOpeningDataSet.Open; 
    Exit; 
    end; 

    FFetchCancel := False; 
    FExecuteOptions := FOpeningDataSet.ExecuteOptions; 
    FFetchProgress := FOpeningDataSet.OnFetchProgress; 
    FFetchComplete := FOpeningDataSet.OnFetchComplete; 
    FRecordsetCreate := FOpeningDataSet.OnRecordsetCreate; 
    FAfterScroll := FOpeningDataSet.AfterScroll; 
    FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking]; 
    FOpeningDataSet.OnFetchProgress := DataSetFetchProgress; 
    FOpeningDataSet.OnFetchComplete := DataSetFetchComplete; 
    FOpeningDataSet.OnRecordsetCreate := DataSetRecordsetCreate; 
    FOpeningDataSet.AfterScroll := DataSetAfterScroll; 
    FOpeningDataSet.CursorLocation := clUseClient; 
    FOpeningDataSet.DisableControls; 
    try 
    DataSetProgressForm.Left := Left + (Width - DataSetProgressForm.Width) div 2; 
    DataSetProgressForm.Top := Top + (Height - DataSetProgressForm.Height) div 2; 
    DataSetProgressForm.cxButton1.OnClick := DataSetProgressClick; 
    DataSetProgressForm.cxButton1.Visible := FShowProgressCancelButton; 

    FOpeningDataSet.Open; 
    DataSetProgressForm.ShowModal; 

    finally 
    FOpeningDataSet.EnableControls; 
    FOpeningDataSet.ExecuteOptions := FExecuteOptions; 
    FOpeningDataSet.OnFetchProgress := FFetchProgress; 
    FOpeningDataSet.OnFetchComplete := FFetchComplete; 
    FOpeningDataSet.OnRecordsetCreate := FRecordsetCreate; 
    FOpeningDataSet.AfterScroll := FAfterScroll; 
    end; 
end; 

procedure TOperatorForm.DataSetProgressClick(Sender: TObject); 
begin 
    FFetchCancel := True; 
end; 

procedure TOperatorForm.DataSetFetchProgress(DataSet: TCustomADODataSet; Progress, MaxProgress: Integer; var EventStatus: TEventStatus); 
begin 
    if FFetchCancel then 
    DataSet.Recordset.Cancel; 
end; 

procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus); 
begin 
    PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0); 
    MessageBeep(MB_ICONEXCLAMATION); 
end; 

procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus); 
begin 
    PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0); 
    MessageBeep(MB_ICONEXCLAMATION); 
end; 

procedure TOperatorForm.DataSetRecordsetCreate(DataSet: TCustomADODataSet; const Recordset: _Recordset); 
begin 
    if Assigned(FRecordsetCreate) then FRecordsetCreate(DataSet, Recordset); 
end; 

procedure TOperatorForm.DataSetAfterScroll(DataSet: TDataSet); 
begin 
    // From TBetterADODataSet 4.04 
    // Ole Willy Tuv's fix 03-10-00 for missing first record 
    with TADODataSet(DataSet) do 
    begin 
    if (eoAsyncFetchNonBlocking in ExecuteOptions) and 
     (Bof or Eof) and 
     (CursorLocation = clUseClient) and 
     (stFetching in RecordSetState) then 
    begin 
     if Recordset.RecordCount > 0 then 
     if Bof then 
      Recordset.MoveFirst 
     else if Eof then 
      Recordset.MoveLast; 
     CursorPosChanged; 
     Resync([]); 
    end; 
    end; 
    if Assigned(FAfterScroll) then 
    FAfterScroll(DataSet); 
end; 

Прогресс:

unit uDataSetProgressForm; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, ComCtrls, ExtCtrls, StdCtrls; 

type 
    TDataSetProgressForm = class(TForm) 
    AnimateProgress: TAnimate; 
    Label1: TLabel; 
    Bevel1: TBevel; 
    Bevel2: TBevel; 
    Button1: TButton; 
    Shape1: TShape; 
    procedure FormCreate(Sender: TObject); 
    procedure FormShow(Sender: TObject); 
    procedure FormHide(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations } 
    end; 

var 
    DataSetProgressForm: TDataSetProgressForm; 

implementation 

{$R *.dfm} 
{$R servertimeout.res} // contains IDR_SERVAVI animation resource 

procedure TDataSetProgressForm.FormCreate(Sender: TObject); 
begin 
    AnimateProgress.ResName := 'IDR_SERVAVI'; 
end; 

procedure TDataSetProgressForm.FormShow(Sender: TObject); 
begin 
    AnimateProgress.Active := True; 
end; 

procedure TDataSetProgressForm.FormHide(Sender: TObject); 
begin 
    AnimateProgress.Active := False; 
end; 

end. 

и DFM

object DataSetProgressForm: TDataSetProgressForm 
    Left = 590 
    Top = 497 
    BorderStyle = bsNone 
    ClientHeight = 104 
    ClientWidth = 205 
    Font.Charset = DEFAULT_CHARSET 
    Font.Color = clWindowText 
    Font.Height = -11 
    Font.Name = 'MS Sans Serif' 
    Font.Style = [] 
    FormStyle = fsStayOnTop 
    OldCreateOrder = False 
    Position = poDefaultSizeOnly 
    OnCreate = FormCreate 
    OnHide = FormHide 
    OnShow = FormShow 
    DesignSize = (
    205 
    104) 
    PixelsPerInch = 96 
    TextHeight = 13 
    object Bevel1: TBevel 
    Left = 0 
    Top = 0 
    Width = 205 
    Height = 104 
    Align = alClient 
    Style = bsRaised 
    end 
    object Bevel2: TBevel 
    Left = 12 
    Top = 12 
    Width = 181 
    Height = 80 
    Anchors = [akLeft, akTop, akRight, akBottom] 
    end 
    object Shape1: TShape 
    Left = 1 
    Top = 1 
    Width = 203 
    Height = 102 
    Anchors = [akLeft, akTop, akRight, akBottom] 
    Brush.Style = bsClear 
    Pen.Color = clWindowFrame 
    end 
    object AnimateProgress: TAnimate 
    Left = 25 
    Top = 23 
    Width = 32 
    Height = 32 
    end 
    object Label1: TLabel 
    Left = 70 
    Top = 31 
    Width = 106 
    Height = 17 
    Hint = 'Selecting data...' 
    Caption = 'Selecting data...' 
    TabOrder = 1 
    end 
    object Button1: TButton 
    Left = 63 
    Top = 64 
    Width = 80 
    Height = 23 
    Caption = 'Cancel' 
    Default = True 
    TabOrder = 2 
    end 
end 
+0

Это все, что я хочу - и я уже делал такие вещи. Но теперь я хочу его в виде функции, которая не возвращается (т. Е. Синхронно). Не стоит беспокоиться о том, что «диалог прогресса» не обновляется, хотя компонент Windows ProgressDialog работает в отдельном потоке. –

+0

@Ian: прочитав обновленный вопрос, я хотел подчеркнуть, что, по крайней мере, в Delphi VCL, если вы хотите, чтобы графический интерфейс был отзывчивым и отображал кнопку отмены щелчка, вы должны вернуть управление контуру сообщения. В Delphi вы можете разрешить это, вызвав ProgressForm.ShowModal, который содержит цикл сообщений внутри, как показано в моем примере выше. И точно функция OpenDataSetInBackground является синхронной: она не возвращается, пока ADODataSet не будет извлечен или отменен. – andrius

 Смежные вопросы

  • Нет связанных вопросов^_^