2015-06-17 15 views
7

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 
+0

'GetCurrentThreadId' identifiziert den aufrufenden Thread. Wenn die IDs nicht übereinstimmen, muss es sich um einen separaten Thread handeln. –

+1

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

+1

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

Antwort

3

nach meiner Erfahrung der einfachere Weg ist entweder zu verwenden:

Synchronize oder TThread.Queue

Dies ist kein Fehler oder zumindest kein VCL Fehler. Dieses Verhalten wird von dem Anbieter behandelt, und wir können nicht sagen, dass es dem specification nicht folgt, da es keine Spezifikation zu dem Verwalten der Asynchronität dieser Ereignisse gibt. Alle die Spezifikation sagt, ist die folgende:

AdAsyncFetchNonBlocking

Zeigt an, dass der Haupt-Thread nie Blöcke beim Abrufen. Wenn die angeforderte Zeile nicht abgerufen wurde, wird die aktuelle Zeile automatisch an das Ende der Datei verschoben.

Dies ist ein Beispiel für Code, um den Haupt-Thread Warnung, dass die Ausführung abgeschlossen ist:

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

Update:

ich dies bestätigt. Es wird für die Versionen 6, 7, XE4 und XE7 funktionieren (ich habe keine andere Version hier). Es ist nichts Falsches daran, Synchronize zu verwenden, um Ihren Code zu injizieren, um ihn in den Hauptthreadkontext auszuführen. Außerdem möchte ich Ihre Aufmerksamkeit auf die Tatsache lenken, dass DataSet lediglich ein Zeiger (eigentlich eine Referenz) auf Ihr ADOQuery-Objekt ist, so dass Sie es nicht notwendigerweise auf Ihre anonyme Methode verweisen müssen, dies ist eine wichtige Tatsache für ältere Versionen wie 6 oder 7, weil anonyme Methoden nicht existieren.

BONUS READING: EVENTS

+0

'OnFetchProgress' und' OnFetchComplete' sind die einzigen beiden Ereignisse, die dieses Verhalten aufweisen. Das Problem ist, dass ich in diesem Fall nicht synchronisieren kann. Dies scheint zumindest nicht als Delphi-TThread, sondern als eine vom OLEDB-Anbieter stammende zu entstehen. Wenn es ein Delphi TThread wäre, könnte ich TThread.Synchronize verwenden, aber ich habe hier keine Flusskontrolle. –

+0

Der Thread stammt nicht von Delphi, wie ich schon sagte. Mit der Verwendung von Synchronize oder TThread.Queue ist nichts falsch, da der Provider-Thread in Ihrem Prozessadressraum erstellt wird. Sie haben weiterhin Zugriff auf Ihre Main Form-Steuerelemente. – EProgrammerNotFound

+0

Welche Art von zusätzlicher Ablaufsteuerung benötigen Sie? – EProgrammerNotFound