![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Как разрулить работу потока в вызовах его методов? [D7, WinXP]
Aleksandr. © (2005-02-11 15:12:00)Есть класс-наследник от TThread, имеющий свойство FList TList, в котором содержатся объекты с информацией для выполнения работы, метод AddQuery для добавления этих объектов в List, и метод,который работает внутри Execute:
type
TQueryThread = class (TThread)
private
FList : TList;
FListCS : TCriticalSection;
procedure DoQuery(aQuery : TExecQuery);
public
procedure AddQuery(aQuery : TExecQuery);
procedure Execute; override;
...
end;
procedure TQueryThread.AddQuery(aQuery : TExecQuery);
begin
FListCS.Acquire;
try
FList.Add(aQuery)
finally
FListCS.Release
end
end;
procedure TQueryThread.Execute;
var
Q : TExecQuery;
begin
repeat
while GetQueryCount>0 do begin // защищенный FListCS FList.Count
FListCS.Acquire;
try
Q:=TExecQuery(FList.Items[0]);
FList.Delete(0)
finally
FListCS.Release
end;
DoQuery(Q)
end;
Suspend
until Terminated
end;
Код DoQuery приводить не буду, он очень большой и длительный по исполнению.
Есть также менеджер потоков TQueryThread, который, получая список из TExecQuery, раздает его по потокам:
procedure TQueryManager.ProcessQueries(L : TList);
var
i, j : integer;
begin
for i:=0 to L.Count-1 do begin
for j:=0 to FQueryThreadList.Count-1 do
if FQueryThreadList.Items[j].ID=TExecQuery(L.Items[i]).ID then begin
FQueryThreadList.Items[j].AddQuery(TExecQuery(L.Items[i]));
if FQueryThreadList.Items[j].Suspended then
FQueryThreadList.Items[j].Resume;
Break
end
end
end;
Вот вся эта схема время от времени дает длительные затыки. Лог выполнения показывает, что происходит примерно следующее:
Добавляется объект запроса в нить, она входит в метод DoQuery,
в это время добавляется еще один объект, и нить прекращает выполнять DoQuery. И обрабатывает все полученные объекты только тогда, когда перестанет вызываться AddQuery, возникнет пауза, а затем добавится еще один объект, добавление которого вызовет резюмирование нити. Может ли такое быть и как это можно разрулить? Догадываюсь, что есть вариант с блокировкой критической секцией между методами AddQuery и DoQuery, но он вызывает у меня сомнения, т.к. теряется смысл в FList, а метод DoQuery очень длительный.
Набережных С. (2005-02-18 14:48:00) [1]
> Digitman © (18.02.05 09:08) [31]
Конечно нужно делать так, как у тебя:) Мой пример и кодом-то назвать нельзя:) Эскиз, набросанный за 5 минут, в котором полно ошибок, как логических, так и технических
Digitman (2005-02-18 09:12:00) [2]да, забыл ..
TQueryThread = class(TThread)
private
procedure MsgQuery(var Message: TMessage); message SN_QUERY;
procedure DoQuery(aQuery : TExecQuery);
protected
procedure Execute; override;
public
destructor Destroy; override;
procedure AddQuery(aQuery : TExecQuery);
end;
..
procedure TQueryThread.MsgQuery(var Message: TMessage);
var
ExecQuery: TExecQuery;
begin
ExecQuery := TExecQuery(Message.wParam);
try
DoQuery(ExecQuery);
finally
ExecQuery.Free;
end;
end;
Digitman (2005-02-18 09:08:00) [3]я бы даже так вот так сделал это :
TQueryThread = class(TThread)
private
procedure DoQuery(aQuery : TExecQuery);
protected
procedure Execute; override;
public
destructor Destroy; override;
procedure AddQuery(aQuery : TExecQuery);
end;
..
destructor TQueryThread.Destroy;
begin
if GetCurrentThreadId <> MainThreadId then
PostThreadMessage(ThreadId, WM_QUIT, 0, 0);
inherited;
end;
procedure TQueryThread.AddQuery(aQuery: TExecQuery);
begin
PostThreadMessage(ThreadID, SN_QUERY, integer(aQuery), 0);
end;
procedure TQueryThread.DoQuery(aQuery: TExecQuery);
begin
// Stub
end;
procedure TQueryThread.Execute;
var
Msg: TMsg;
begin
try
try
while not Terminated and GetMessage(Msg, 0, 0, 0) do
Dispatch(Msg.Message);
finally
while PeekMessage(Msg, INVALID_HANDLE_VALUE, SN_QUERY, SN_QUERY, PM_REMOVE) do
try
TExecQuery(Msg.wParam).Free;
except
end;
end;
except
end;
end;
Набережных С. (2005-02-18 08:13:00) [4]А мож примерно так, а?
const
SN_QUERY = WM_USER + 400;
SN_QUIT = SN_QUERY + 1;
type
TExecQuery = class
// Stub
end;
TQueryThread = class(TThread)
private
procedure DoQuery(aQuery : TExecQuery);
protected
procedure Execute; override;
public
procedure AddQuery(aQuery : TExecQuery);
procedure Terminate;
end;
implementation
{ TQueryThread }
procedure TQueryThread.AddQuery(aQuery: TExecQuery);
begin
PostThreadMessage(ThreadID, SN_QUERY, integer(aQuery), 0);
end;
procedure TQueryThread.DoQuery(aQuery: TExecQuery);
begin
// Stub
end;
procedure TQueryThread.Execute;
var
Msg: TMsg;
begin
while GetMessage(Msg, 0, 0, 0) do
begin
case Msg.message of
SN_QUERY: DoQuery(TExecQuery(Msg.wParam));
SN_QUIT:
begin
while PeekMessage(Msg, 0, SN_QUERY, SN_QUERY, PM_REMOVE) do
TExecQuery(Msg.wParam).Free;
Break;
end;
end;
end;
end;
procedure TQueryThread.Terminate;
begin
PostThreadMessage(ThreadID, SN_QUIT, 0, 0);
inherited;
end;
Aleksandr. (2005-02-17 16:44:00) [5]Ну ничего у меня не выходит, млин! Какие странные ступоры выплывают. Мало того, по условию теперь в свойства нити необходимо добавить еще один List для объектов другого типа, да еще и булевое свойство для третьего вида обработки. Я попытался сделать их как интерпретацию объектов первого вида:
в новом методе в FUpdList добавляются объекты другого типа, после чего создается TExecQuery с предопределенными свойствами и вызывается опять же AddQuery. Ну а в обработчике DoQuery в зависимости от свойств рассматриваемого TExecQuery вызывается тот или иной метод. Все это вроде бы и работает, но иногда все равно происходит нестыковка - менеджер добавляет запросы, а потоки их уже не обнаруживают. Удалось только снизить вероятность такого затыка защитой одной критической секцией методов AddQuery и всего содержимого участка while WaitForSingleObject...end, что опять же привело к неприятному эффекту - все потоки, размещающие через менеджер потоков свои запросы, вынуждены дожидаться разблокирования секций на период весьма не быстрой работы DoCurrentQuery. Код теперь выглядит так
В менеджере:
// этот метод добавляет нитям в очередь запросы
procedure TQueryManager.ProcessNewQuery(Q: TExecQueryList);
var
i : integer;
A : TQueryThread;
T : smallint;
begin
FResetCS.Enter;
try
T:=-1;
try
for i:=0 to Q.QueryCount-1 do begin
try
T:=Q.Items[i].qID;
A:=Threads[T]; // метод возвращает нужную нить
if Assigned(A) then
A.AddQuery(Q.Items[i])
else begin
DoLog(Format('Не удалось найти обработчик для %d, ручной сброс ожидания',[T]));
Q.Items[i].qHandled:=true;
SetEvent(Q.Items[i].qHandle)
end
except
on E:Exception do begin
DoLog(Format('Не удалось найти обработчик для %d '+E.Message,[T]));
Q.Items[i].qHandled:=true;
SetEvent(Q.Items[i].qHandle)
end
end
end
except
on E:Exception do
LogDBError(T,'Process new query: '+E.Message)
end
finally
FResetCS.Leave
end
end;
// этот метод добавляет нитям в очередь списки объектов для обработки другого типа
procedure TQueryManager.ProcessATIUpdate(IDT: word; EvList: TATIUpdateList);
var
A : TQueryThread;
begin
FResetCS.Enter;
try
if EvList.Count>0 then begin
A:=Threads[IDT];
if Assigned(A) then
A.AddUpdates(EvList)
else
EvList.Clear
end
finally
FResetCS.Leave
end
end;
в нити:
// этот метод добавляет в свой список элемент запроса
procedure TQueryThread.AddQuery(Query: TExecQuery);
begin
DoLog(Format('Запрос добавлен для %d',[Query.qUserID]));
FExecCS.Acquire;
try
DoLog('Добавление запроса');
FQueryList.Add(Query);
SetEvent(FExecEvent)
finally
FExecCS.Release
end
end;
// этот метод добавляет в список объектов пакет объектов для одного использования
procedure TQueryThread.AddUpdates(EvList: TATIUpdateList);
var
Q : TExecQuery;
begin
if NOT ((NOT Assigned(EvList)) OR (EvList.Count=0) OR NOT Assigned(FUpdateList)) then begin
FUpdateCS.Enter;
try
while EvList.Count>0 do begin
FUpdateList.Add(EvList.Items[0].GetCopy);
EvList.Delete(0)
end
finally
FUpdateCS.Leave
end;
Q:=TExecQuery.Create(0,qiOtherInfo,0,0);
AddQuery(Q)
end
end;
procedure TQueryThread.Execute;
var
Q : TExecQuery;
begin
try
Coinitialize(nil);
try
FSQLUpdator:=TATISQLUpdator.Create(FIDT);
try
LoadCashedData;
CalculateMemBuffers;
CalCulateIndexes;
while WaitForSingleObject(FExecEvent,INFINITE)=WAIT_OBJECT_0 do try
if Terminated then
Break;
FExecCS.Acquire;
try
CheckFileIndex;
with FQueryList.LockList do try
while Count>0 do begin
Q:=Items[0];
if Q.qType=qiOtherInfo then begin
case Q.qID of
0 : ProcessUpdates;
1 : ProcessClearing;
end;
Q.Free // убиваем запрос, потому как создан самой нитью
end
else
DoQuery(Q); // иначе запрос будет убит потоком, который его создал и передал менеджеру
Delete(0)
end
finally
FQueryList.UnlockList
end;
// ReSetEvent(FExecEvent) // вроде как не надо, потому что создан =CreateEvent(nil, false, False, PChar(IntToStr(Self.ThreadID)+'thread'));
finally
FExecCS.Release
end
except
on E:Exception do
DoLog('Ошибка цикла, '+E.Message)
end
finally
FreeAndNil(FSQLUpdator)
end
finally
CoUninitialize
end
except
on E:Exception do
DoLog('Ошибка Execute '+E.Message)
end
end;
Leonid Troyanovsky (2005-02-14 23:19:00) [6]
> Alex Konshin © (14.02.05 22:11) [27]
> (я не очень уверен в действенности проверки Terminated =
> true
> в многопроцессорных системах).
>
> А какая разница? Это всего лишь переменная.
А, действительно... Уговорил :)
Внешний - while & Event с автосбросом,
внутренний - while not Terminated.
--
Regards, LVT.
Alex Konshin (2005-02-14 22:11:00) [7](я не очень уверен в действенности проверки Terminated = true
в многопроцессорных системах).
А какая разница? Это всего лишь переменная.
Что происходит в данном коде, если (не) вызывается ResetEvent?
Я ж объяснил, что произойдет. Будет обрабатываться только первый запрос из очереди. Чтоб этого не происходило, можно организовать еще один цикл. Здесь же предлагаются фокусы с event с ручным сбросом и лишние системные вызовы. Мне такой подход не нравится. Что делать тебе - дело твое.
Leonid Troyanovsky (2005-02-14 22:03:00) [8]
> Aleksandr. (14.02.05 12:34) [25]
> Ох... спасибо всем... Теперь буду пытаться понять все это...
> Особенно про ResetEvent. Я уже не первый год юзаю систему
> CreateEvent-SetEvent-CloseHandle, но впервые узнаю, что
> Event надо еще и сбрасывать. Что происходит в данном коде,
> если (не) вызывается ResetEvent?
Чаще используется CreateEvent with bManualReset=False.
А в этом случае событие сбрасывается автоматически тогда,
когда его дождался любой поток (который его WaitFor*).
В упомянутом коде ручной сброс предлагался для обеспечения
быстрого отклика на Terminate (or Free) потока.
Иначе, поток завершится лишь после исчерпания очереди
(я не очень уверен в действенности проверки Terminated = true
в многопроцессорных системах).
--
Regards, LVT.
Aleksandr. (2005-02-14 12:34:00) [9]Ох... спасибо всем... Теперь буду пытаться понять все это... Особенно про ResetEvent. Я уже не первый год юзаю систему CreateEvent-SetEvent-CloseHandle, но впервые узнаю, что Event надо еще и сбрасывать. Что происходит в данном коде, если (не) вызывается ResetEvent?
Verg (2005-02-12 15:41:00) [10]Я бы сделал объект очередь. У нее public полем event с ручным сбросом.
Ее методы add, extractTop, getcount защищены критической секцией.
add
<Вход в КС>
добавляет новый элемент в конец очереди и взводит event (setEvent)
<Выход из КС>
extractTop
<Вход в КС>
Извлекает указатель на верхний элемент (Result:=), удаляет этот элемент и если очередь пуста, сбрасывает event (ResetEvent).
<Выход из КС>
Появления элементов в очереди ожидаем WaitFor... ф-циями
Queue : TQueue;
while WaitForSingleObject(Queue.Event, INFINITE) = WAIT_OBJECT_0 do
begin
Query := Queue.extractTop();
DoQuery(Query);
end;
Eraser (2005-02-12 15:18:00) [11]Aleksandr.
Также в коллекции JEDI компонентов, есть целая вкладка для работы с потоками, по-моему то что надо.
Alexander Panov (2005-02-12 14:57:00) [12]Aleksandr. (11.02.05 15:12)
Может быть, натолкнет на какие-либо мысли вот эта реализация твоей задачи:
http://home.ural.ru/~panov/projects/threadspool/threadspool.html
Набережных С. (2005-02-12 14:04:00) [13]>Leonid Troyanovsky © (12.02.05 12:26) [20]
>APC один из самых эффективных механизмов.
Имхо, эффективность у него точно такая же, как и у очередей сообщений, а вот работать с ним менее удобнее.
>Alex Konshin © (12.02.05 11:26) [19]
>Не, можно, конечно, и очередь Windows задействовать, и еще чего-нибудь, только зачем?
Использование очереди сообщений, как и APC, позволит отказаться и от TList, и от критических секций, и от эвентов.
Leonid Troyanovsky (2005-02-12 12:26:00) [14]
> Alex Konshin © (12.02.05 11:26) [19]
> Вставлять в твоем случае можно в любой момент кроме try...finally.
> Также, как и в моем случае.
Да, конечно, со вставкой был не прав.
Еще не проснулся, sorry.
> Насчет APC - опять-таки ненужное усложнение.
APC один из самых эффективных механизмов.
Широко исползуется в системе- для передачи одного параметра -
прямо, для большего - вместе с memory mapped file (секцией).
Да и зачем городить свою очередь, когда есть готовая.
И выглядит все очень понятно: посылаем запрос рабочему потоку
- выполнить DoQuery, DoQuit и т.д.
А рабочий поток просто сидит в SleepEx(INFINITE, True).
Ну, конечно, для потоков-объектов нужно сделать threadvar с
ссылкой на себя, или передавать эту ссылку в TExecQuery.
> Просто не понимаю, зачем извращаться, когда все прямолинейно
> и ясно? Тем более, что это и эффективнее. Не, можно, конечно,
> и очередь Windows задействовать, и еще чего-нибудь, только
> зачем?
В данном случае можно как угодно, бо главная проблема лишь в
длительности DoQuery. Т.е., чего толку от замечательности
очереди, если она постоянно непуста.
--
Regards, LVT.
Alex Konshin (2005-02-12 11:26:00) [15]Вставлять в твоем случае можно в любой момент кроме try...finally.
Также, как и в моем случае.
Насчет APC - опять-таки ненужное усложнение.
Для организации списка в моем случае достаточно одного поля в TExecQuery и пару переменных-указателей на первый элемент и на последнее поле связи.
Просто не понимаю, зачем извращаться, когда все прямолинейно и ясно? Тем более, что это и эффективнее. Не, можно, конечно, и очередь Windows задействовать, и еще чего-нибудь, только зачем?
Leonid Troyanovsky (2005-02-12 11:01:00) [16]
> Alex Konshin © (12.02.05 05:49) [17]
> Хотя да... У тебя вместо простой проверки в цикле наличия
> запросов,
> делается замысловатые пассы с event с ручным сбросом.
> Совершенно излишнее усложнение. Лишние обращения к системе
> - лишние накладные расходы, к тому и сложнее получается.
Просто такая схема обеспечит возможность добавления в список
после любого DoQuery. И реакцию на Terminate (не уверен, что
проверки Terminated внутри цикла достаточно для
мультипроцессорных систем).
Ведь, по условию, DoQuery длительная операция.
> Достаточно простого event с автоматом и двойной цикл while.
> Еще вместо TList я бы предложил использовать связный список.
Можно вообще без списка - хватит очереди APC потока.
Но, видимо, в оптимизации здесь нуждается тот самый DoQuery.
--
Regards, LVT.
Alex Konshin (2005-02-12 05:49:00) [17]Хотя да... У тебя вместо простой проверки в цикле наличия запросов,
делается замысловатые пассы с event с ручным сбросом.
Совершенно излишнее усложнение. Лишние обращения к системе - лишние накладные расходы, к тому и сложнее получается. Достаточно простого event с автоматом и двойной цикл while.
Еще вместо TList я бы предложил использовать связный список.
Alex Konshin (2005-02-12 05:29:00) [18]Leonid Troyanovsky © (12.02.05 04:43) [15]
Неверно.
После выхода из WaitForSingleObject мы должны обработать всезапросы, находящиеся в очереди и только после этого опять уйти на ожидание. В противном случае при почти одновременном получении более одного запроса будет исполнятся только первый, а остальные так и останутся в очереди.
Leonid Troyanovsky (2005-02-12 04:43:00) [19]
> Aleksandr. (12.02.05 03:39) [14]
> Спасибо за разъяснения! Только я со Suspend не понял. Мне
> не надо завершаться, мне надо переходить в ожидание, когда
> будет добавлен следующий запрос. Вы имеете в виду, что надо
> в констракторе создать эвент, а по выполнении DoQuery переходить
> в инфинитный WaitForSingleObject установки этого эвента?
Например, делаем EventDoit с ручным сбросом и
procedure TQueryThread.Execute;
..
begin
while WaitForSingleObject(EventDoit, INFINITE) = WAIT_OBJECT_0 do
begin
if Terminated then
Break;
Q := nil;
with FList.Lock do
try
if Count = 0 then
ResetEvent(EventDoit)
else
begin
Q:=TExecQuery(Items[0]);
Delete(0);
end;
finally
FList.UnLockList;
end;
if Assigned(Q) then
DoQuery(Q);
end;
end;
Тогда, при добавлении в список нужно делать SetEvent.
Также SetEvent в Terminate:
SetEvent(EventDoit);
inherited;
и
destructor TQueryThread.Destroy; // override;
begin
Terminate; // т.к. он не виртуальный
inherited;
end;
> А что до Q.Free, так этого вообще тут не надо, как я понимаю
> - объект создается в другом потоке, содержит в себе событие,
> которое надо установить по окончании DoQuery, и в нем же
> уничтожится, этого события (точнее, их комплекта от всех
> объектов переданного менеджеру в ProcessQueries списка)
> поток-создатель ожидает в WaitForMultipleObjects, за которым
> дестракторы и вызовет.
Проще всего, когда эти объекты созданы в первичном (VCL)
потоке.
Тогда уничтожать их можно передавая указатель в Post(Thread)
Message окну или в Application.OnMessage.
Т.е., специфические ожидания и не потребуются.
--
Regards, LVT.
Aleksandr. (2005-02-12 03:39:00) [20]Leonid Troyanovsky © :
Спасибо за разъяснения! Только я со Suspend не понял. Мне не надо завершаться, мне надо переходить в ожидание, когда будет добавлен следующий запрос. Вы имеете в виду, что надо в констракторе создать эвент, а по выполнении DoQuery переходить в инфинитный WaitForSingleObject установки этого эвента?
А что до Q.Free, так этого вообще тут не надо, как я понимаю - объект создается в другом потоке, содержит в себе событие, которое надо установить по окончании DoQuery, и в нем же уничтожится, этого события (точнее, их комплекта от всех объектов переданного менеджеру в ProcessQueries списка) поток-создатель ожидает в WaitForMultipleObjects, за которым дестракторы и вызовет.