Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 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
1-1172136353
Medusa__
2007-02-22 12:25
2007.04.22
Создание компонент динамически.


2-1175622889
Guest007
2007-04-03 21:54
2007.04.22
Как запустить файл?


1-1172676041
DenisNew
2007-02-28 18:20
2007.04.22
ширина закладки ttabsheet


2-1175711062
sholomak
2007-04-04 22:24
2007.04.22
Работа с PDF


15-1175066614
Kerk
2007-03-28 11:23
2007.04.22
MoiKrug.ru





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский