Обнаружение вторичного контекста потока в Delphi - PullRequest
4 голосов
/ 04 ноября 2010

В Delphi 2009 и Windows API есть способ обнаружить, что определенный фрагмент кода выполняется в контексте вторичного потока? В псевдокоде я бы хотел сказать:

procedure DoSomething;
begin
  if InvokedBySecondaryThread then
    DoIt_ThreadSafeWay
  else
    DoIt_RegularWay;
end;

Это для библиотеки журналов, которую я писал и использовал годами, и сейчас пытаюсь приспособиться к ситуации, когда одна процедура может быть вызвана из нескольких потоков. Мой "обычный путь" не безопасен. Я знаю, как сделать это потокобезопасным, но я бы хотел использовать этот метод только тогда, когда это действительно необходимо.

Пояснение (не обязательно чтение: -)

Это сводится к выбору между использованием SendMessage и PostMessage для отправки зарегистрированного сообщения нескольким получателям, таким как файл журнала, консоль или элемент управления VCL. Использование PostMessage означает, что сообщения не будут получены, пока выполняется длительная операция блокировки, что несколько противоречит цели ведения журнала, особенно когда используется для обозначения прогресса. Я полагаю, что мог бы защитить вызов SendMessage с критической секцией, но я бы предпочел сделать это только тогда, когда это действительно необходимо.

Я знаю о глобальном var IsMultiThread в system.pas, но это только скажет мне, что приложение запустило вторичные потоки. Это могут быть потоки, созданные сторонними библиотеками, и поэтому они никогда не получат доступ к «моему» коду, поэтому их существование не повлияет на мою логику регистрации.

Мне бы очень хотелось использовать один и тот же низкоуровневый код библиотеки независимо от того, вызывается ли он из одного или нескольких потоков. Например, было бы легко вызывать модифицированные, поточно-безопасные процедуры ведения журнала из вторичных потоков, но это дублировало бы большой код, и мне все равно пришлось бы запомнить , чтобы всегда делать правильные вещи.

@ Ливен: В настоящее время логика ведения журнала выглядит следующим образом, несколько упрощенно

Я хочу, чтобы регистрация данных была максимально безболезненной, с минимальным кодом установки и не беспокоясь об управлении временем жизни объекта, поэтому библиотека предоставляет только несколько перегруженных вспомогательных процедур, таких как

procedure Log( const msgText : string; level : TLogLevel = lvNotice ); overload;
procedure Log( const msgText : string; Args : array of const; level : TLogLevel = lvNotice ); overload;
etc, including specialized routines that log a StringList, a boolean, an Exception and so on

Практически все остальное происходит при реализации устройства. Все вспомогательные процедуры в конечном итоге вызывают

procedure _LogPostMessage( const msgText : string; level : TLogLevel );

который (а) проверяет, был ли инициализирован объект-диспетчер синглтона; (б) создает экземпляр объекта TLogMessagePacket (контейнер для текста сообщения, отметки времени и т. д.) и, наконец, (в) выполняет SendMessage или PostMessage для отправки «пакета» диспетчеру (который имеет дескриптор окна для его получения). ).

Затем существует группа классов, происходящих от абстрактного класса TLogReceiver. Один такой класс получает зарегистрированные сообщения и записывает их в файл, другой обновляет TMemo и т. Д. Я создаю экземпляры конкретных получателей, которые хочу использовать в проекте, и регистрирую их у диспетчера, которому они принадлежат с этого момента.

Когда диспетчер получает сообщение «пакет», он по очереди передает его каждому получателю, а затем освобождает его.

Так что я, вероятно, исправил рассмотренный выше способ мышления, поэтому я не совсем понимаю вашу идею, когда вы говорите код, передающий объект диспетчера в библиотеку журналов, должен выбирать один в зависимости от контекста потока, Диспетчер действительно главный движок библиотеки, и только один существует за один раз.

Ответы [ 2 ]

14 голосов
/ 04 ноября 2010
  if GetCurrentThreadID = MainThreadID then begin
// in main thread
  end
  else begin
// in secondary thread
  end; 
0 голосов
/ 04 ноября 2010

По вашему запросу я добавил пример кода, чтобы показать, что я имел в виду.

interface
  procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload;
...

implementation    

  procedure Log( const msgText : string; level : TLogLevel = lvNotice; dispatcher: IDispatcher = nil); overload;
  var
    dp: IDispatcher;
  begin
    if dispatcher = nil then dp := CreateDefaultDispatcher
    else dp := dispatcher;

    dp.msgText := msgText;
    dp.level := level;
    ...
  end;

procedure _LogPostMessage( dispatcher: IDispatcher );
begin
  dispatcher.LogPostMessage            
end;

Теперь, что вам это дает?

Не так много, кроме дополнительной сложности, как уже отмечалось, но это облегчает тестирование _LogPostMessage.

Посмотрите на это так: как вы собираетесь написать модульный тест, чтобы убедиться, что сообщение действительно публикуется?

...