Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2007.04.22;
Скачать: CL | DM;

Вниз

Ведение лога в многопоточном приложении   Найти похожие ветки 

 
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;
Скачать: CL | DM;

Наверх




Память: 0.59 MB
Время: 0.049 c
2-1175436969
sat
2007-04-01 18:16
2007.04.22
вставка на ассемблере


2-1175691056
cosinus
2007-04-04 16:50
2007.04.22
Хм...Вопрос по глобальному хуку на клавиатуру


1-1172328976
EgorovAlex
2007-02-24 17:56
2007.04.22
Как лучше сделать межпотоковое взаимодействие: есть несколько


2-1175156344
АндрейК
2007-03-29 12:19
2007.04.22
Уроветь TreeView


15-1174664197
Alekc
2007-03-23 18:36
2007.04.22
bluetooth и сервисы