Форум: "Основная";
Текущий архив: 2007.04.22;
Скачать: [xml.tar.bz2];
ВнизВедение лога в многопоточном приложении Найти похожие ветки
← →
Cooller (2007-02-20 22:52) [0]Есть серверное приложение, активно работающая одновременно с несколькими клиентами. Некоторые события необходимо выводить в лог (как в файл, так и в RichEdit).
Вот сама процедура:procedure ATL(msg:string; showmsg:boolean=false);
begin
try
Append(F);
WriteLn(F,DateTimeToStr(now)+": "+msg);
CloseFile(F);
except
end;
end;
FormCreate:AssignFile(F,opt.curlogname);
{$I-}
Append(F);
{$I+}
If IOResult<>0 then Rewrite(F);
CloseFile(F);
В RichEdit добавляю просто с помощью Lines.Add
Так вот, несмотря на то, что у меня во всех процедурах и функциях стоят оработчики try except end, с выводом сообщения об ошибке в лог, периодически получаю "Access Violation". Может быть проблема и не в добавлении в лог, но сейчас пересматриваю весь код и возникла мысль, что коллизии могут возникать и в этом месте.
Подскажите как правильно организовать вывод информации?
← →
DrPass © (2007-02-20 22:55) [1]
> В RichEdit добавляю просто с помощью Lines.Add
Только через Synchronize
> try
> Append(F);
> WriteLn(F,DateTimeToStr(now)+": "+msg);
> CloseFile(F);
> except
> end;
А здесь - вместо try...except использовать критические секции
← →
Cooller (2007-02-20 23:19) [2]
> Только через Synchronize
У меня:socks:array of record
s:TClientSocket;
...
end;
Инициализация:for i:=0 to length(socks)-1 do
begin
socks[i].s:=TClientSocket.Create(nil);
with socks[i].s do
begin
...
onRead:=SockRead;
onDisconnect:=SockDisconnect;
onError:=SockError;
end;
end;procedure TfrmMain.SockRead(Sender: TObject; Socket: TCustomWinSocket);
begin
try
...
ATL("example");
...
except
SEM("SockRead");
end;
end;
← →
Loginov Dmitry © (2007-02-20 23:25) [3]> Так вот, несмотря на то, что у меня во всех процедурах и
> функциях стоят оработчики try except end, с выводом сообщения
> об ошибке в лог, периодически получаю "Access Violation".
Выкинь отовсюду try except end и начни исправлять свои баги.
← →
Cooller (2007-02-20 23:50) [4]
> Выкинь отовсюду try except end и начни исправлять свои
> баги.
Если бы они были, то конструкция хотя бы иногда срабатывала
← →
Cooller (2007-02-20 23:51) [5]В том и дело, что ошибки другого рода, которые проявляются через Access Violation. Для того, чтобы понять как их устранить - я и создал тему.
← →
Суслик © (2007-02-21 02:17) [6]
> Cooller (20.02.07 23:51) [5]
ну ситуация такова.
vcl не является многопоточной. если ты хочешь вызвать метод из библиотеки vcl (читай из визуального компонента), то ты обязан это делать в контексте основного потока. для того, чтобы выполнить код из доп. потока в контексте основного потока тебе нужно использовать метод synchronize из класса tthread.
вообще использование vcl из разных потоков без синхронизации не обязательно влечет av. но делать этого все же нельзя - ибо vcl не спроектирована как многопоточная.
вполне возможно, что у тебя есть некоторые другие ошибки в программе, приводящие к av.
для синхронизации общих данных используй критические секции.
← →
Cooller (2007-02-21 09:57) [7]2 Суслик
Спасибо за ответ. Хотел спросить еще одну вещь... вместо synchronize можно использовать sendmessage?
← →
Суслик © (2007-02-21 10:27) [8]Ну можно, только нужно хорошо понимать, что делаешь.
Только вопрос - зачем.
synchronize реализован как раз на одном из методов (postmessage or sendmessage - зависит от версии дельфи).
причем они это сделали очень неплохо - весьма продуманный код.
я бы тебе не советовал делать такую синхронизацию самому.
← →
DrPass © (2007-02-21 10:30) [9]
> вместо synchronize можно использовать sendmessage?
Можно и так. Или PostMessage. С точки зрения производительности - более эффективный вариант
← →
Хинт © (2007-02-21 10:36) [10]
> Ну можно, только нужно хорошо понимать, что делаешь. Только
> вопрос - зачем.
В [2] я описал ситуацию. Synchronize это ведь метод TThread...
← →
Суслик © (2007-02-21 11:57) [11]1 Используй критические секции при доступе к файлу.
2 При доступе к richedit используй postmessage. событие лови в форме. только договорись о том, как данные (строки) передавать.
← →
Leonid Troyanovsky © (2007-02-21 12:58) [12]
> Суслик © (21.02.07 11:57) [11]
> 2 При доступе к richedit используй postmessage. событие
> лови в форме.
А зачем в форме? Richedit вполне понимает EM_*.
Которые ему SendMessage, конечно.
--
Regards, LVT.
← →
Reindeer Moss Eater © (2007-02-21 13:05) [13]unit LogSupport;
interface
procedure Log(const AMsg:string; const AParam:string = "");
implementation
uses Classes, SysUtils, SyncObjs;
var LogName:string;
cs : TCriticalSection = nil;
procedure Log(const AMsg : string; const AParam : string = "");
var F:Text;
begin
cs.Enter;
Assign(F,LogName);
try
if FileExists(LogName) then Append(F) else Rewrite(F);
Writeln(F,FormatDateTime("dd.mm.yyyy hh:mm:ss",Now),#9,AMsg,#9,AParam);
finally
Close(F);
cs.Leave;
end;
end;
initialization
cs := TCriticalSection.Create;
LogName:=ChangeFileExt(ParamStr(0),".log");
finalization
cs.Free;
end.
← →
Cooller (2007-02-21 15:01) [14]2 Reindeer Moss Eater
Может дурацкий вопрос, но у меня почему-то на эту тему постоянная паранойя (после некоторых лекций в универе). Если Log, к примеру, вызывается по 5 раз в секунду, то не "жирно" ли каждый раз вызывать FileExists? =)
← →
Суслик © (2007-02-21 15:04) [15]проведи анализ - используй QueryPerformanceFrequency и QueryPerformanceCounter и пойми, сколько времени занимает вызов FileExists в общем времени работы алгоритма.
← →
Reindeer Moss Eater © (2007-02-21 15:06) [16]то не "жирно" ли каждый раз вызывать FileExists? =)
А там прикинь, еще каждый раз с объектом синхронизации телодвижения вставлены. Вообще кошмар.
← →
Cooller (2007-02-21 15:34) [17]Вообще какие бы могли дать общие советы? Использовать как можно меньше глобальный переменных? В некоторых случаях они необходимы, получается потоки должны работать с ними только с синхронизацией? Или в общем случае она не нужна, а есть только некоторые исключения?
← →
Cooller (2007-02-21 15:41) [18]Просто программа хоть и не большая, но кода все-таки на 1500 строк. Вроде бы закрыл все "критические" места, но все равно получаю Access Violation. Даже и не знаю как выловить причину.
← →
Суслик © (2007-02-21 15:55) [19]Поставь себе средство какое-нибудь, чтобы стек вызовов при исключении показывало. Можешь в jedi покопаться - я иногда этим пользуюсь.
----------
Общее правило таково. Многопоточное программирование сильно отличается от однопоточного в части философии. В таком программировании могут быть всякие разные неожиданные эффекты. Не всегда доступ из нескольких потоков БЕЗ синхронизации запрещен. Например ты спокойно можешь обращаться и менать глоб. булеву переменную без всякой синхронизации. Если не ошибаюсь, то и integer можешь. А вот int64 уже нет, ибо она делается двумя машинными командами.
Это я к тому, что многопоточное программирование просто так снаскока не возьмешь. Нужно читать, изучать чужой код (да хоть тот же TThread и прочие многопоточные вещи из rtl и vcl). Можешь Рихтера почитать - программирование в win32. У него много про межпоточнкую синхронизацию - мозги точно разовьешь.
-----
Я понимаю, что у тебя конкретная проблема. Но и решение ее должно быть кокретно. Т.е. что-либо внятное дистанционно сказать сложно.
-----
Приведенный выше код с крит. секциями не очень хорош лучше такunit LogSupport;
interface
procedure Log(const AMsg:string; const AParam:string = "");
implementation
uses Classes, SysUtils, SyncObjs;
var LogName:string;
cs : TCriticalSection = nil;
procedure Log(const AMsg : string; const AParam : string = "");
var
F:Text;
begin
cs.Enter;
try
Assign(F,LogName);
if FileExists(LogName) then Append(F) else Rewrite(F);
try
Writeln(F,FormatDateTime("dd.mm.yyyy hh:mm:ss",Now),#9,AMsg,#9,AParam);
finally
Close(F);
end;
finally
cs.Leave;
end;
end;
initialization
cs := TCriticalSection.Create;
LogName:=ChangeFileExt(ParamStr(0),".log");
finalization
cs.Free;
end.
← →
Суслик © (2007-02-21 15:59) [20]
> [12] Leonid Troyanovsky © (21.02.07 12:58)
> А зачем в форме? Richedit вполне понимает EM_*.
> Которые ему SendMessage, конечно.
Согласен. Но мой вариант имхо более гобок, т.к. перед добавлением может понадобиться доп. обработка.
К тому же я winapi плохо знаю, чтобы советовать события контролам кидать :)
← →
Cooller (2007-02-21 16:24) [21]2 Суслик
Спасибо за советы. Буду разбираться.
А что не все так просто я понял когда еще несколько лет назад знакомился с ASM и необходимо было подменять вектора прерываний. Правда тогда все решалось использованием cli и sti =)
← →
Игорь Шевченко © (2007-02-21 16:33) [22]
> К тому же я winapi плохо знаю, чтобы советовать события
> контролам кидать
А собственно это тот же Рихтер описывает. SendMessage блокирует отправляющий поток до тех пор, пока принимающий не обработает сообщение.
Следующий код вполне себе работает (мне проще в ListBox отправлять :)unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
btnNew: TButton;
procedure btnNewClick(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses
Thread;
{$R *.dfm}
procedure TForm1.btnNewClick(Sender: TObject);
begin
TFooThread.Create(ListBox1);
end;
end.
unit thread;
interface
uses
Classes, StdCtrls;
type
TFooThread = class(TThread)
private
FLog: TListBox;
protected
procedure Execute; override;
public
constructor Create (ALog: TListBox);
end;
implementation
uses
Windows, Messages, SysUtils;
{ TFooThread }
constructor TFooThread.Create(ALog: TListBox);
begin
FLog := ALog;
inherited Create (false);
end;
procedure TFooThread.Execute;
var
I: Integer;
begin
I := 0;
while not Terminated do begin
SendMessage(FLog.Handle, LB_ADDSTRING, 0, Integer(PChar(IntToStr(I))));
Inc(I);
Sleep(100);
end;
end;
end.
← →
Суслик © (2007-02-21 16:33) [23]2Coller
Для того, чтобы сильно продвинуться в многопоточности нужно:
1. что-то почитать
2. обязательно смотреть код.
мне в свое время очень помогло изучение *кода* класса TMultiReadExclusiveWriteSynchronizer.
Очень ставит мозги. Я не все понял, конечно, но полезно однозначто было.
← →
novill © (2007-02-21 16:36) [24]а как насчет использования
flush
илиCreateFile с флагом FILE_FLAG_WRITE_THROUGH
← →
Cooller (2007-02-21 17:23) [25]Во многих местах происходит вызов вот этой процедуры:
procedure TfrmMain.AddInfo(Text: string; Color: TColor = clBlack; AddText: string = ""; dbg:boolean=false);
Хочу её тело скопировать в другую, а тут же сделать вызов SendMessage. Посоветуйте как лучше передать параметры? Через глобальную переменную и сделать критическую секцию? Что-то вроде:private
{ Private declarations }
procedure WMSyncMessage(var Msg: TMessage); message WM_SYNC_MSG;
...
const
WM_SYNC_MSG = WM_USER+...;
...
var
ai:record
...
end;
...
procedure TfrmMain.AddInfo(Text: string; Color: TColor = clBlack; AddText: string = ""; dbg:boolean=false);
SLock.Enter;
try
ai.text:=text;
ai.color:=color;
ai.addtext:=addtext;
ai.dbg:=dbg;
SendMessage(frmMain.Handle,WM_SYNC_ADD_INFO,0,0);
finally
SLock.Leave;
end;
end;
procedure TfrmMain.WMSyncMessage(var Msg: TMessage);
begin
SyncAddInfo(ai.text,ai.color,ai.addtext,ai.dbg);
end;
← →
Cooller (2007-02-21 17:23) [26]Во многих местах происходит вызов вот этой процедуры:
procedure TfrmMain.AddInfo(Text: string; Color: TColor = clBlack; AddText: string = ""; dbg:boolean=false);
Хочу её тело скопировать в другую, а тут же сделать вызов SendMessage. Посоветуйте как лучше передать параметры? Через глобальную переменную и сделать критическую секцию? Что-то вроде:private
{ Private declarations }
procedure WMSyncMessage(var Msg: TMessage); message WM_SYNC_MSG;
...
const
WM_SYNC_MSG = WM_USER+...;
...
var
ai:record
...
end;
...
procedure TfrmMain.AddInfo(Text: string; Color: TColor = clBlack; AddText: string = ""; dbg:boolean=false);
SLock.Enter;
try
ai.text:=text;
ai.color:=color;
ai.addtext:=addtext;
ai.dbg:=dbg;
SendMessage(frmMain.Handle,WM_SYNC_ADD_INFO,0,0);
finally
SLock.Leave;
end;
end;
procedure TfrmMain.WMSyncMessage(var Msg: TMessage);
begin
SyncAddInfo(ai.text,ai.color,ai.addtext,ai.dbg);
end;
← →
Cooller (2007-02-21 17:25) [27](WM_SYNC_MSG=WM_SYNC_ADD_INFO)
← →
Суслик © (2007-02-21 17:25) [28]
> Посоветуйте как лучше передать параметры? Через глобальную
> переменную и сделать критическую секцию? Что-то вроде:
если это sendmessage, то ты можешь передавать указатель в параметрах сообщения. только реши, кто будет данные уничтожать после использования (если это надо).
← →
Суслик © (2007-02-21 17:26) [29]
> [22] Игорь Шевченко © (21.02.07 16:33)
Спасибо, познавательно.
Посмотрел исходники - не первый раз убеждаюсь, что vcl это неплохо :)
Хорошо же они продумали надостройку над winapi - можно напрямую пользоваться, можно через delphi класс.
← →
Игорь Шевченко © (2007-02-21 17:40) [30]
> Посмотрел исходники - не первый раз убеждаюсь, что vcl это
> неплохо :)
> Хорошо же они продумали надостройку над winapi - можно напрямую
> пользоваться
Ну да, собственно, еще начиная с первой версии Delphi такие действия были оправданы, так как до того народ кроме api ничего не знал :)
← →
Leonid Troyanovsky © (2007-02-22 11:48) [31]
> Игорь Шевченко © (21.02.07 16:33) [22]
> SendMessage(FLog.Handle, LB_ADDSTRING, 0, Integer(PChar(IntToStr(I))));
Тут есть один ньюанс, как-то здесь обсуждался.
Можно представить себе ситуацию, когда, скажем при RecreateWnd,
Handle = 0, и вызов приведет к созданию окна во вторичном потоке.
Я бы здесь предпочел передачу в поток самого значения хендла.
Ну, а если необходимо следить за валидностью этого хендла,
понадобится сабкласирование контрола для ловли WM_DESTROY.
Или же установить хук на первичный поток.
--
Regards, LVT.
← →
Игорь Шевченко © (2007-02-22 12:14) [32]Leonid Troyanovsky © (22.02.07 11:48) [31]
> Можно представить себе ситуацию, когда, скажем при RecreateWnd,
>
> Handle = 0, и вызов приведет к созданию окна во вторичном
> потоке.
> Я бы здесь предпочел передачу в поток самого значения хендла.
>
И что будет с переданным значением Handle после RecreateWnd ?
Сообщения будут отправляться в воздух ?
Для исключения подобной ситуации, мне кажется, лучше будет отправлять сообщение самой форме, на которой находится контрол для протокола, чтобы она в своем потоке добавляла в протокол. Но вот стоит ли овчинка выделки (дополнительного кода) - не уверен.
← →
Суслик © (2007-02-22 13:02) [33]Я бы предпочел посылку форме, ибо это не трубует доп. знаний vcl в части ее особенности работы в многопоточном режиме.
← →
Игорь Шевченко © (2007-02-22 13:12) [34]Суслик © (22.02.07 13:02) [33]
Это требует дополнительного кода. А оно надо ?
← →
Суслик © (2007-02-22 13:43) [35]Писать нужно так (имхо, конечно) как знаешь. Если знаешь одну какую-то методику хорошо - ей и нужно пользоваться. Вполне возможно, что прийдет время относительного затишься, когда можно пересмотреть код, сделать необходимый рефакторинг и подумать о сокращении или украшении кода. Но если ты в потоке и делаешь конкретную задачу нужно, то пользоваться проверенными методами.
← →
Leonid Troyanovsky © (2007-02-22 19:55) [36]
> Игорь Шевченко © (22.02.07 12:14) [32]
> Для исключения подобной ситуации, мне кажется, лучше будет
> отправлять сообщение самой форме, на которой находится контрол
> для протокола, чтобы она в своем потоке добавляла в протокол.
Для исключения достаточно отправлять сообщение окну
Application, которое никогда не пересоздается.
> Но вот стоит ли овчинка выделки (дополнительного кода)
Трудно сказать.
Т.к., между оптимистическим и пессемистическим подходом -
бездна промежуточных.
Вообще-то, предназначение моего комментария было лишь в том,
чтобы заметить, что в случае с многими потоками все оказывается
несколько сложней, чем могло бы показаться.
--
Regards, LVT.
← →
Чапаев © (2007-02-22 20:23) [37]А я бы всё это через EventLog() писал в системный журнал, а потом при необходимости выборку в файл делал...
← →
Чапаев © (2007-02-22 20:23) [38]Тьфу, блин. Через ReportEvent().
← →
Игорь Шевченко © (2007-02-24 20:12) [39]Leonid Troyanovsky © (22.02.07 19:55) [36]
> Для исключения достаточно отправлять сообщение окну
> Application, которое никогда не пересоздается.
А если требуется отправить синхронное сообщение ?
← →
Leonid Troyanovsky © (2007-02-24 23:56) [40]
> Игорь Шевченко © (24.02.07 20:12) [39]
> > Для исключения достаточно отправлять сообщение окну
> > Application, которое никогда не пересоздается.
> А если требуется отправить синхронное сообщение ?
Синхронное, асинхронное - все возможно.
Главное, что оно не пересоздается.
--
Regards, LVT.
Страницы: 1 2 вся ветка
Форум: "Основная";
Текущий архив: 2007.04.22;
Скачать: [xml.tar.bz2];
Память: 0.57 MB
Время: 0.05 c