2015-06-17 4 views
7

При использовании 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 
+0

'GetCurrentThreadId' идентифицирует вызывающий поток. Если идентификаторы не совпадают, это должен быть отдельный поток. –

+1

Вы можете использовать дескриптор окна формы (или выделить его) на «PostMessage» с собственным сообщением пользователя в обработчике события «OnFetchComplete», который фактически выходит за пределы контекста основного потока. – kobik

+1

Это событие действительно работает в другом потоке, как сказал Кобик, ваш лучший вариант - использовать «PostMessage», вы можете найти образец [здесь] (http://stackoverflow.com/a/26058386/800214). – whosrdaddy

ответ

3

в моем опыте простой способ заключается в использовании либо:

Synchronize или TThread.Queue

Это не ошибка или, по крайней мере, не VCL ошибка. Такое поведение обрабатывается провайдером, и мы не можем сказать, что он не следует за specification, потому что нет спецификации о том, как управлять асинхронностью этих событий. Все говорит спецификации заключается в следующем:

adAsyncFetchNonBlocking

Указывает, что основной поток никогда не блокирует, а не извлечения. Если запрошенная строка не была восстановлена, текущая строка автоматически перемещается в конец файла.

Это пример кода предупреждения основной поток, что выполнение завершено:

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

Update:

Я подтвердил это. Он будет работать для версий 6, 7, XE4 и XE7 (у меня нет другой версии здесь). Нет ничего плохого в использовании Synchronize для ввода кода для выполнения в контексте основного потока. Кроме того, я хочу обратить ваше внимание на то, что DataSet - это просто указатель (фактически ссылка) на ваш объект ADOQuery, поэтому вам необязательно ссылаться на него на ваш анонимный метод, это важный факт для более старых версий например 6 или 7, потому что анонимных методов не существует.

BONUS READING: EVENTS

+0

'OnFetchProgress' и' OnFetchComplete' - это только два события, которые демонстрируют это поведение. Проблема в том, что я не знаю, как синхронизировать в этом случае. По крайней мере, это, по-видимому, происходит как Delphi TThread, но как источник, созданный поставщиком OLEDB. Если бы это был Delphi TThread, я мог бы использовать TThread.Synchronize, но у меня нет управления потоком. –

+0

Нить не родом из Delphi, как я уже сказал. Нет ничего плохого в использовании Synchronize или TThread.Queue, потому что поток провайдера создается в вашем адресном пространстве процесса, у вас все еще будет доступ к элементам управления основной формой. – EProgrammerNotFound

+0

Какое дополнительное управление потоком вам нужно? – EProgrammerNotFound

Смежные вопросы