ADO:如何同步执行查询取消的能力?

时间:2010-11-08 20:19:25

标签: c++ delphi asynchronous ado

现在我有一个使用ADO运行查询的函数,并返回recordset

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

现在我面临着如何定期检查用户是否已取消(以及知道查询是否已完成,或者是否已发生错误)以及如何成为一名优秀的程序员而无需轮询的情况。

知道错误发生的唯一方法是在Recordset的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

3 个答案:

答案 0 :(得分:1)

  1. 可能无法在每个数据库引擎中提供进度信息和正常取消查询。他们需要在服务器和客户端上提供数据库支持。例如,Oracle允许取消查询,但没有“进度”信息,但是读取了V $ SESSION_LONGOPS视图。当然,您可以终止会话,但它会回滚整个会话,而不仅仅是取消执行查询。
  2. 通常,如果数据库支持这种功能,则查询将在一个等待结果的单独线程中运行。这样主线程仍然可以获得用户输入或读取并显示进度信息(除非在某种回调中返回)。如果用户取消查询,则发出适当的调用以停止操作,允许查询线程返回,通常线程将收到一个状态代码,告诉发生了什么。
  3. 了解ADO如何实现异步操作:http://msdn.microsoft.com/en-us/library/ms681467(VS.85).aspx
  4. 还有一个FetchProgress()事件可以帮助你,如果你不想采用线程方式(如果可能的话,甚至可以取消查询)

答案 1 :(得分:1)

为了使GUI响应按钮点击,您应该将控制权返回到窗口的消息循环。 while循环 while(rs.State和(adStateConnecting + adStateExecuting + adStateFetching)&lt;&gt; 0)不会将控制权返回给消息循环,从而阻止GUI。


以下是使用异步ADO查询的Delphi代码的摘录。此代码不允许非模态提取数据,但可确保在获取数据期间重新绘制主窗体,并允许取消查询。

通过设置:
实现异步执行和提取  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

答案 2 :(得分:0)

如果是Delphi,您可以删除TTimer组件并使用它来检查HasUserCancelled值是否为True。我没有在我面前使用Delphi所以我以后必须发布一个例子。

编辑:

这是一个TTimer OnTimer事件的示例,它检查当前时间和最后一个活动时间,以确定如果程序保持“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;