При использовании TADOQuery
с [eoAsyncFetchNonBlocking]
и присоединением к OnFetchComplete
случае я обнаружил, что OnFetchComplete
не выполняется в главном потоке (протестировано в xe4 и X Е8). Я предполагаю, что это ошибка *, так как большинство из нас будет работать в пользовательском интерфейсе этого типа событий. Я считаю, что это источник некоторых проблем в более крупном проекте, и мне нужно обходное решение.Асинхронных TADOQuery не синхронизирован с основным потоком
[EDIT] * После прочтения документации ADO, я знаю, что это может быть не ошибка, но проблема многопоточности остается.
Есть ли элегантный способ заставить код в этом обработчике выполнить на основной теме? Я не хочу использовать таймер (но если это единственное решение, я возьму его). В качестве альтернативы, есть ли объект синхронизации ADO, который я могу дождаться здесь или какая-либо другая форма сигнализации для провайдера 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
[EDIT] Было сделано предложение использовать TThread.Sychronize
, но это не Delphi Thread.
Если GetCurrentThreadId
не является достаточным доказательством того, что обработчик вызываются из другого потока здесь является callstacks основного и проблемного нити (я добавил сон в основном потоке для хорошей меры)
основного потока сон
: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
Другой поток вызова обработчика
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' идентифицирует вызывающий поток. Если идентификаторы не совпадают, это должен быть отдельный поток. –
Вы можете использовать дескриптор окна формы (или выделить его) на «PostMessage» с собственным сообщением пользователя в обработчике события «OnFetchComplete», который фактически выходит за пределы контекста основного потока. – kobik
Это событие действительно работает в другом потоке, как сказал Кобик, ваш лучший вариант - использовать «PostMessage», вы можете найти образец [здесь] (http://stackoverflow.com/a/26058386/800214). – whosrdaddy