(Delphi 2009) idIRC, MDI и проблемы с зависанием - PullRequest
1 голос
/ 14 ноября 2009

Я работаю над IRC-клиентом. Я столкнулся с серьезным препятствием, которое до тех пор, пока я не смог обойти. Я покажу код ниже. У меня проблемы с созданием дочерних окон MDI в обработчиках событий idIRC.

Например, если я хочу создать новую форму канала (FrmChannel), я могу легко сделать это, вызвав процедуру создания, когда я ловлю команду '/ join'.

Однако, если я хочу сделать это правильно и подождать, пока я действительно не присоединюсь к каналу, и получить подтверждение этого от сервера (обработав его в обработчике события onjoin), тогда мой вызов в мою форму процедура создания приводит к зависанию приложения.

То же самое касается окон состояния. Например, если я добавлю вызов процедуры создания окна состояния в событие onclick TButton, хорошо. Дочерняя форма создана. Однако, если я попробую то же самое, когда получу личное сообщение, проверив обработчик события ... Приложение зависнет, без исключения и без дочернего MDI.

Вот соответствующий код (для решения этого я буду иметь дело только с окном запроса).

Во-первых, фактическое создание MDI Child выглядит следующим образом. У меня есть TComponentList здесь для управления списком этого класса формы (на случай, если вам интересно). Здесь есть и другие вещи, которые также отслеживают форму, хотя их комментирование не предотвращает зависание (я пробовал).

procedure TFrmMain.NewQuery(const Server, MsgFrom: String);
var
Child: TFrmMessage;
TN: TTreeNode;
begin

///
/// Create form, set some data so we can reference it later.
///
///

  Child := TFrmMessage.Create(Application);
//  QueryManager.Add(Child); //TComponent List -- Used to find the Form Later On

  with Child do
  begin
   MyServer := Server; {What server this PM window is on}
   QueryWith := MsgFrom; {nickaname of the other person}
   Caption := MsgFrom; {Asthetic}
  end;

  Child.Echo('*** Conversation with ' + MsgFrom); //Herro World

  ///
  ///  The following code is working.
  ///  I'm pretty sure it's not causing the hangs.
  ///

  TN := GetNodeByText(ChanServTree, Server, True); {Find our parent node}

  with ChanServTree.Items.AddChild(TN, MsgFrom) do
  begin
   Selected := True;
   Tag := 2; {TYPE OF QUERY}
   Data := Pointer(Integer(Child)); //Pointer to Form we created
  end;

end;

Вот обработчик событий для моего компонента IRC:

procedure TFrmMain.IRCPrivateMessage(ASender: TIdContext; const ANicknameFrom,
  AHost, ANicknameTo, AMessage: string);
  var
  CheckVr: String;
  aThread: TNQThread;
begin
  //DEBUG:
(StatusManager[0] as TFrmStatus).Echo('From: ' + ANickNameFrom + 'AMESSAGE: ' + '''' +AMessage + '''');

///
/// Handle Drone Version Requests!
///  This is REQUIRED on servers like irc.blessed.net - or they won't let you join
///  channels! - It's part of the Registration proccess
///

{The Drones on some server's don't follow specifications, so we need to search
hard for their presence}

CheckVr := AMessage;

StringReplace(CheckVr,' ','',[rfReplaceAll, rfIgnoreCase]);
StringReplace(CheckVr,#1,'',[rfReplaceAll, rfIgnoreCase]);
(StatusManager[0] as TFrmStatus).Echo('Message was: ' + '''' + CheckVr + '''');

if Trim(CheckVr) = 'VERSION' then
begin
 IRC.CTCPReply(ANickNameFrom,'VERSION','mIRC v6.01 Khaled Mardam-Bey');
 (StatusManager[0] as TFrmStatus).Echo('*** Sent Version Reply to ' + ANickNameFrom);

 exit; {Because if we don't, this could mess things up}
end;

  ///
  /// The Following code sends the PM to the appropriate window.
  ///  If that window does not exist, we will create one first.
  ///


  if Pos('#',Amessage) = 1 then
   begin
    //Handled Elsewhere
   end else {is PM}
   begin

     if FindQueryFrm(ANickNameTo,IRC.Host) = nil then
    begin

    NewQuery(IRC.Host, ANickNameFrom);
      exit;
     end;

   end;

//  FindChannelFrm(ANickNameTo,IRC.Host).ChannelMessage(ANicknameFrom, AMessage);

end;

Я пытался закомментировать различные части кода, чтобы попытаться найти причину зависания. Зависание вызвано Child: = TFrmMessage.Create (Application); позвони конкретно. Что дает?

Я пытался реализовать потоки, чтобы понять, может ли это быть проблемой. Если вы думаете, что проблема именно в этом, мне понадобится помощь с многопоточностью, потому что, очевидно, хотя код компилируется, я все еще вызываю что-то неправильно (потому что даже моя многопоточная версия зависает).

Заранее спасибо.

Ответы [ 2 ]

4 голосов
/ 14 ноября 2009

Как я уже говорил вам в alt.comp.lang.borland-delphi ранее сегодня , проблема в том, что Indy запускает свои обработчики событий в том же потоке, что и блокирующий сокет звонки, которые не совпадают с вашим GUI. Все операции с графическим интерфейсом должны выполняться в одном потоке, но вы создаете новое окно в потоке сокета.

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

Если у вас есть достаточно свежая версия Delphi, вы можете попробовать метод TThread.Queue, который работает так же, как Synchronize, за исключением того, что вызывающий поток этого не делает блок ожидания основного потока для запуска данного метода. Они оба имеют одно и то же ограничение относительно параметров своих методов; они принимают только метод с нулевым параметром. Это усложняет передачу дополнительной информации для метода, который будет использоваться при его вызове. Это особенно плохо для методов , поставленных в очередь , поскольку любые дополнительные данные, которые вы предоставляете для них, должны оставаться нетронутыми в течение всего времени, необходимого для его запуска основным потоком; вызывающий поток должен удостовериться, что он не перезаписывает лишние данные до вызова метода из очереди.

Лучшим планом, вероятно, будет просто опубликовать сообщение в определенном окне основного потока. Application.MainForm является заманчивой целью, но формы Delphi могут быть воссозданы без предварительного уведомления, поэтому любой дескриптор окна, используемый вашими другими потоками, может оказаться недействительным во время попытки опубликовать сообщение. И чтение свойства MainForm.Handle по требованию также небезопасно, поскольку, если форма не имеет дескриптора в то время, она будет создана в контексте потока сокета, что позже вызовет всевозможные проблемы. Вместо этого попросите главный поток создать новое выделенное окно для получения сообщений потока с AllocateHWnd.

Как только у вас есть цель для сообщений, к которым вы можете перейти, вы можете организовать темы для их публикации и получения. Определите значение сообщения и опубликуйте его с помощью PostMessage.

const
  am_NewQuery = wm_App + 1;

PostMessage(TargetHandle, am_NewQuery, ...);

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

type
  PNewQuery = ^TNewQuery;
  TNewQuery = record
    Host: string;
    FromNickname: string;
  end;

Подготовьте и опубликуйте сообщение так:

procedure NewQuery(const Server, MsgFrom: string);
var
  Data: PNewQuery;
begin
  New(Data);
  Data.Host := Server;
  Data.FromNickname := MsgFrom;
  PostMessage(TargetHandle, am_NewQuery, 0, LParam(Data));
end;

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

class procedure TSomeObject.HandleThreadMessage(var Message: TMessage);
var
  NewQueryData: PNewQuery;
begin
  case Message.Msg of
    am_NewQuery: begin
      NewQueryData := PNewQuery(Message.LParam);
      try
        Child := TFrmMessage.Create(NewQueryData.Host, NewQueryData.FromNickname);
        TN := GetNodeByText(ChanServTree, NewQueryData.Host, True); // Find parent node
        with ChanServTree.Items.AddChild(TN, NewQueryData.FromNickname) do begin
          Selected := True;
          Tag := 2; // TYPE OF QUERY
          Data := Child; // reference to form we created
        end;
      finally
        Dispose(NewQueryData);
      end;
    end;
    else
      Message.Result := DefWindowProc(TargetHandle, Message.Msg, Message.WParam, Message.LParam);
  end;
end;

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

1 голос
/ 14 ноября 2009

Прошло много времени с тех пор, как я программировал в Delphi и боролся с подобными проблемами ...

В Java уведомления о сокетах происходят в совершенно другом потоке, чем тот, который поддерживает GUI, и вы практическиЗапрещено вносить изменения в графический интерфейс извне потока GUI (но вам даны механизмы, чтобы по закону попросить поток GUI сделать мод).В Delphi все события происходят из одного и того же цикла обработки событий, но все же ... Я бы чувствовал тошноту, требуя серьезного обновления графического интерфейса, такого как открытие окна на основе события сокета.

Что бы я хотелпопробуйте сделать так: получить событие comm, чтобы оставить уведомление в очереди или что-то в этом роде, и заставить поток GUI обработать его в обработчике onIdle или что-то в этом роде.

Это удар в темноте,хоть.Прими мою рекомендацию с большим количеством соли!

...