synchonized Wenn TADOQuery
mit [eoAsyncFetchNonBlocking]
Verwendung und Anbringung an OnFetchComplete
Ereignis fand ich, dass OnFetchComplete
nicht in dem Haupt-Thread (getestet in XE4 und XE8) ausgeführt wird. Ich nehme an, dies ist ein Bug *, da die meisten von uns in der Benutzeroberfläche für diese Art von Ereignis arbeiten werden. Ich glaube, dass dies die Ursache einiger Probleme in einem größeren Projekt ist und ich brauche einen Workaround.Asynchronous TADOQuery des OnFetchComplete nicht auf Haupt-Thread
[EDIT] * Nach dem Lesen der ADO-Dokumentation weiß ich, dass dies kein Bug sein kann, aber das Multithreading-Problem bleibt.
Gibt es eine elegante Möglichkeit, Code in diesem Handler zu erzwingen, damit er auf dem Hauptthread ausgeführt wird? Ich möchte keinen Timer verwenden (aber wenn das die einzige Lösung ist, nehme ich es). Alternativ gibt es ein ADO-Synchronisierungsobjekt, auf das ich hier warten kann, oder eine andere Form der Signalisierung an den ADO-Anbieter?
Hier ist ein vereinfachtes Beispiel, das das Problem zeigt. Mein Projekt ist komplexer mit einer Factory, die diese Datensätze erstellt und füllt, aber es wäre hier analog, den Datensatz an ein Raster innerhalb von anzuhängen.
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.
Und die DFM hat einfach die Abfrage-Set mit ExecuteOptions = [eoAsyncFetchNonBlocking]
und OnFetchComplete
behandelt.
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
[EDIT] wurde ein Vorschlag gemacht TThread.Sychronize
zu verwenden, aber das ist kein Delphi Thread.
Wenn die GetCurrentThreadId
nicht ausreichend Beweise dafür, dass der Handler von einem anderen Thread hier genannt wird, die Callstacks des Haupt- und problematischen Thread (Ich habe einen Schlaf im Hauptthread für eine gute Maßnahme)
Hauptthread Schlaf
: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:\windows\SysWOW64\user32.dll
:759b932c ; C:\windows\SysWOW64\user32.dll
:759b9529 ; C:\windows\SysWOW64\user32.dll
:77d107d6 ntdll.KiUserCallbackDispatcher + 0x36
:759be4a9 ; C:\windows\SysWOW64\user32.dll
:711f19e4 ; C:\windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.17810_none_a9edf09f013934e0\comctl32.dll
:711f1a7b ; C:\windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.9600.17810_none_a9edf09f013934e0\comctl32.dll
:759b8e71 user32.CallNextHookEx + 0xb1
:759b90d1 ; C:\windows\SysWOW64\user32.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:\windows\SysWOW64\user32.dll
:759ba66f ; C:\windows\SysWOW64\user32.dll
:759ba6e0 user32.DispatchMessageW + 0x10
:005bb158 TApplication.ProcessMessage + $F8
:00040000
Andere Gewinde Aufruf des Handlers
Unit6.TForm6.QueryFetchComplete($288B3E0,nil,esOK)
Data.Win.ADODB.TCustomADODataSet.FetchComplete(nil,89849068,Pointer($3299D8) as _Recordset)
:6b7ab81d ; C:\Program Files (x86)\Common Files\System\ado\msado15.dll
:6b7ab4b6 ; C:\Program Files (x86)\Common Files\System\ado\msado15.dll
:6b7a17c8 ; C:\Program Files (x86)\Common Files\System\ado\msado15.dll
:6b7b616f ; C:\Program Files (x86)\Common Files\System\ado\msado15.dll
:69038991 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:69038bd6 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:69038d54 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:69037a02 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:69021205 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:69038034 ; C:\Program Files (x86)\Common Files\System\msadc\msadce.dll
:77a07c04 KERNEL32.BaseThreadInitThunk + 0x24
:77d2ad1f ntdll.RtlInitializeExceptionChain + 0x8f
:77d2acea ntdll.RtlInitializeExceptionChain + 0x5a
'GetCurrentThreadId' identifiziert den aufrufenden Thread. Wenn die IDs nicht übereinstimmen, muss es sich um einen separaten Thread handeln. –
Sie können das Fenster-Handle des Formulars (oder eines davon) zu "PostMessage" mit Ihrer eigenen Benutzernachricht im Ereignishandler "OnFetchComplete" verwenden, der außerhalb des Kontexts des Haupt-Threads ausgeführt wird. – kobik
Dieses Ereignis läuft tatsächlich in einem anderen Thread, wie Kobik sagte, Ihre beste Option ist 'PostMessage' zu verwenden, Sie können ein Beispiel [hier] finden (http://stackoverflow.com/a/26058386/800214). – whosrdaddy