异步TADOQuery的OnFetchComplete未同步到主线程

[eoAsyncFetchNonBlocking]使用TADOQuery并附加到OnFetchComplete事件时,我发现OnFetchComplete未在主线程中执行(在XE4和XE8中测试过)。 我认为这是一个错误*,因为我们大多数人都会在这些类型的事件中在UI上工作。 我认为这是大型项目中一些问题的根源,我需要一个解决方法。

[编辑]在阅读ADO文档后,我知道这可能不是一个错误,但多线程问题依然存在。

有没有一种优雅的方式来强制在这个处理程序中的代码在主线程上执行? 我不想使用计时器(但如果这是我将采取的唯一解决方案)。 或者,是否有ADO Synchronization对象,我可以在此处等待或向ADO提供程序发送其他形式的信号?

这是一个简单的示例,显示了这个问题。 我的项目对于创建和填充这些数据集的工厂来说更加复杂,但在此将类似的数据集附加到ADOQuery1FetchComplete的网格中。

unit Unit4;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Data.Win.ADODB, Vcl.StdCtrls;

type
  TForm4 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ADOQuery1: TADOQuery;
    ADOConnection1: TADOConnection;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ADOQuery1FetchComplete(DataSet: TCustomADODataSet;
      const Error: Error; var EventStatus: TEventStatus);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FMainThreadID : DWORD;
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure TForm4.ADOQuery1FetchComplete(DataSet: TCustomADODataSet;
  const Error: Error; var EventStatus: TEventStatus);
begin
  Assert(FMainThreadID = GetCurrentThreadId); //this assertion fails!
  // I need UI code here to run  FMainThreadID
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
   ADOQuery1.Open;
end;


procedure TForm4.FormCreate(Sender: TObject);
begin
    FMainThreadID := GetCurrentThreadId;
end;

end.

而dfm只是简单地设置了ExecuteOptions = [eoAsyncFetchNonBlocking]OnFetchComplete处理的查询。

object Form4: TForm4
  Left = 0
  Top = 0
  Caption = 'Form4'
  ClientHeight = 186
  ClientWidth = 258
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 24
    Top = 88
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object ADOQuery1: TADOQuery
    Connection = ADOConnection1
    ExecuteOptions = [eoAsyncFetchNonBlocking]
    OnFetchComplete = ADOQuery1FetchComplete
    Parameters = <>
    SQL.Strings = (
      'SELECT * FROM TABLENAME')
    Left = 144
    Top = 16
  end
  object ADOConnection1: TADOConnection
    Connected = True
    ConnectionString = 
      'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security In' +
      'fo=False;Initial Catalog=DBNAME;Data Source=.INSTANCENAME'
    LoginPrompt = False
    Provider = 'SQLOLEDB.1'
    Left = 40
    Top = 16
  end
end

[编辑]建议使用TThread.Sychronize ,但这不是一个Delphi线程。

如果GetCurrentThreadId不足以证明处理程序是从另一个线程调用的,则这里是主线程和有问题的线程的调用堆栈(为了更好的度量,我在主线程中添加了一个sleep)

主线程正在睡觉

:77d0c7bc ntdll.ZwDelayExecution + 0xc
:7745104f KERNELBASE.Sleep + 0xf
Unit6.TForm6.btnQueryClick($32BC00)
Vcl.Controls.TControl.Click
Vcl.StdCtrls.TCustomButton.Click
Vcl.StdCtrls.TCustomButton.CNCommand(???)
Vcl.Controls.TControl.WndProc((48401, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.Controls.TWinControl.WndProc((48401, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.StdCtrls.TButtonControl.WndProc((48401, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.Controls.TControl.Perform(???,???,7275840)
Vcl.Controls.DoControlMsg(???,(no value))
Vcl.Controls.TWinControl.WMCommand((273, (), 1344, 0, (), 7275840, 0))
Vcl.Forms.TCustomForm.WMCommand((273, (), 1344, 0, (), 7275840, 0))
Vcl.Controls.TControl.WndProc((273, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.Controls.TWinControl.WndProc((273, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.Forms.TCustomForm.WndProc((273, 1344, 7275840, 0, 1344, 0, (), 1344, 111, (), 0, 0, ()))
Vcl.Controls.TWinControl.MainWndProc(???)
System.Classes.StdWndProc(2829362,273,1344,7275840)
:759b8e71 user32.CallNextHookEx + 0xb1
:759b90d1 ; C:windowsSysWOW64user32.dll
:759b932c ; C:windowsSysWOW64user32.dll
:759b9529 ; C:windowsSysWOW64user32.dll
:77d107d6 ntdll.KiUserCallbackDispatcher + 0x36
:759be4a9 ; C:windowsSysWOW64user32.dll
:711f19e4 ; C:windowsWinSxSx86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.17810_none_a9edf09f013934e0comctl32.dll
:711f1a7b ; C:windowsWinSxSx86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.17810_none_a9edf09f013934e0comctl32.dll
:759b8e71 user32.CallNextHookEx + 0xb1
:759b90d1 ; C:windowsSysWOW64user32.dll
:759bddd5 user32.CallWindowProcW + 0x95
Vcl.Controls.TWinControl.DefaultHandler(???)
:00532947 TWinControl.DefaultHandler + $EB
:00532836 TWinControl.WndProc + $5CA
:00544cdd TButtonControl.WndProc + $71
:004c9162 StdWndProc + $16
:759b8e71 user32.CallNextHookEx + 0xb1
:759b90d1 ; C:windowsSysWOW64user32.dll
:759ba66f ; C:windowsSysWOW64user32.dll
:759ba6e0 user32.DispatchMessageW + 0x10
:005bb158 TApplication.ProcessMessage + $F8
:00040000

其他线程调用处理程序

Unit6.TForm6.QueryFetchComplete($288B3E0,nil,esOK)
Data.Win.ADODB.TCustomADODataSet.FetchComplete(nil,89849068,Pointer($3299D8) as _Recordset)
:6b7ab81d ; C:Program Files (x86)Common FilesSystemadomsado15.dll
:6b7ab4b6 ; C:Program Files (x86)Common FilesSystemadomsado15.dll
:6b7a17c8 ; C:Program Files (x86)Common FilesSystemadomsado15.dll
:6b7b616f ; C:Program Files (x86)Common FilesSystemadomsado15.dll
:69038991 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:69038bd6 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:69038d54 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:69037a02 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:69021205 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:69038034 ; C:Program Files (x86)Common FilesSystemmsadcmsadce.dll
:77a07c04 KERNEL32.BaseThreadInitThunk + 0x24
:77d2ad1f ntdll.RtlInitializeExceptionChain + 0x8f
:77d2acea ntdll.RtlInitializeExceptionChain + 0x5a

根据我的经验,更简单的方法是使用:

同步或TThread.Queue

这不是一个错误,或者至少不是一个VCL错误。 这种行为是由提供者处理的,我们不能说它没有遵循规范,因为没有关于如何管理这些事件的异步的规范。 所有规格说明如下:

那么adAsyncFetchNonBlocking

表示检索时主线程永远不会阻塞。 如果所请求的行未被检索到,则当前行自动移动到文件末尾。

这是警告执行完成的主线程的代码示例:

procedure TForm1.ADOQuery1FetchComplete(DataSet: TCustomADODataSet;
  const Error: Error; var EventStatus: TEventStatus);
begin
  TThread.Synchronize(nil,
    procedure
    begin
      ShowMessage('FetchData Completed');
    end
    );
end;

更新:

我证实了这一点。 它将适用于版本6,7,XE4和XE7(我在这里没有其他版本)。 使用Synchronize注入代码以执行到主线程上下文没有任何问题。 另外,我想引起您的注意,DataSet仅仅是一个指向ADOQuery对象的指针(实际上是一个引用),所以您不一定必须在匿名方法中引用它,这对旧版本来说是一个重要的事实像6或7,因为匿名方法不存在。

奖金阅读:活动

链接地址: http://www.djcxy.com/p/27111.html

上一篇: Asynchronous TADOQuery's OnFetchComplete not synchonized to main thread

下一篇: Distributed Transaction on Linked Server between sql server and mysql