Usando TADOQuery
con [eoAsyncFetchNonBlocking]
e fissaggio a OnFetchComplete
evento ho trovato che OnFetchComplete
non viene eseguito nel thread principale (testato in XE4 e XE8). Presumo che questo sia un bug *, dato che molti di noi lavoreranno nell'interfaccia utente per questo tipo di evento. Credo che questo sia la fonte di alcuni problemi in un progetto più ampio e ho bisogno di una soluzione alternativa.Asynchronous TADOQuery non synchonized a thread principale
[EDIT] * Dopo aver letto la documentazione di ADO, so che questo potrebbe non essere un bug, ma il problema del multithreading rimane.
C'è un modo elegante per forzare l'esecuzione del codice in questo gestore sul thread principale? Non voglio usare un timer (ma se è l'unica soluzione che prenderò). In alternativa, c'è un oggetto di sincronizzazione ADO che posso aspettare qui o qualche altra forma di segnalazione al provider ADO?
Ecco un esempio semplificato che mostra che il problema. Il mio progetto è più complesso con una fabbrica che crea e riempie questi set di dati, ma sarebbe analogo qui collegare il set di dati a una griglia all'interno di 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.
E il DFM deve semplicemente la query set con ExecuteOptions = [eoAsyncFetchNonBlocking]
e OnFetchComplete
maneggiato.
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] Un suggerimento è stato fatto da usare TThread.Sychronize
, ma questo non è un filo Delphi.
Se il GetCurrentThreadId
non è una prova sufficiente che il gestore si chiama da un altro thread qui sono gli stack di chiamate del thread principale e problematico (ho aggiunto un sonno nel thread principale per buona misura)
thread principale sonno
: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
altro thread chiamante il gestore
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' identifica il thread chiamante. Se gli ID non corrispondono, deve essere un thread separato. –
È possibile utilizzare l'handle di finestra del modulo (o assegnarne uno) a 'PostMessage' con il proprio messaggio utente nel gestore di eventi' OnFetchComplete', che di fatto viene eseguito al di fuori del contesto del thread principale. – kobik
Questo evento si svolge effettivamente in un'altra discussione, come ha detto Kobik, la tua migliore opzione è usare 'PostMessage', puoi trovare un esempio [qui] (http://stackoverflow.com/a/26058386/800214). – whosrdaddy