正确的方法来释放和重用一个线程

时间:2014-04-19 11:48:39

标签: multithreading delphi deadlock reusability

我正在使用Delphi XE2,我的应用程序用于通知twitter / rss中的新记录。在我的应用程序中,我使用2个线程从twitter和amp;中获取一些数据。 rss每秒。

以下是代码:

类型部分:

  TWarframeEvent=record
    GUID: String;
    EventType: Byte; // 0 = unknown; 1 = alert; 2 = invasion; 3 = infestation
    Planet: String;
    Mission: String;
    EventDate: TDateTime;
    Time: Integer;
    RewardCredits: LongWord;
    RewardOther: String;
    RewardOtherAmount: Integer;
    Notified: Boolean;
    ItemIndex: Integer;
    Hidden: Boolean;
  end;

  TWarframeNotifyEvent=record
    NotifyTimeLeft: LongWord;
    ID: Integer;
    FlashOnTaskbar: Boolean;
    PlaySound: Boolean;
    Volume: Integer;
    TrayPopupBalloon: Boolean;
  end;

  TWarframeEventList=record
    WarframeEvent: Array of TWarframeEvent;
    WarframeEventCount: Integer;
    NotifyEvent: TWarframeNotifyEvent;
  end;


  TUpdateFromTwitterThread=class(TThread)
    TwitterURL: String;
    Procedure Execute; override;
  end;

  TUpdateFromRSSThread=class(TThread)
    RSS_URL: String;
    Procedure Execute; override;
  end;

var section(module)

  WarframeEventList: TWarframeEventList;

实施部分:

procedure TForm1.TimerUpdateEventsTimer(Sender: TObject);
begin
  UpdateFromTwitterThread:=TUpdateFromTwitterThread.Create(True);
  UpdateFromTwitterThread.TwitterURL:=form2.EditAlertsURL.Text;
  UpdateFromTwitterThread.Start;
  UpdateFromRSSThread:=TUpdateFromRSSThread.Create(True);
  UpdateFromRSSThread.RSS_URL:=form2.EditInvansionsURL.Text;
  UpdateFromRSSThread.Start;
end;

procedure TUpdateFromTwitterThread.Execute;
var
  HTTPClient: TIdHTTP;
  IOHandler: TIdSSLIOHandlerSocketOpenSSL;
  S, S2: String;
  i, l: Integer;
  NewAlertDate: TDateTime;
  ErrorLogFile: TextFile;
begin
  HTTPClient:=TIdHTTP.Create(nil);
  IOHandler:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    try
      HTTPClient.IOHandler:=IOHandler;
      HTTPClient.HandleRedirects:=True;
      S:=HTTPClient.Get(self.TwitterURL);
    except
      Assign(ErrorLogFile, AppPath+'Error.log');
      if not(FileExists(AppPath+'Error.log')) then
        Rewrite(ErrorLogFile);
      Append(ErrorLogFile);
      Writeln(ErrorLogFile, DateTimeToStr(Now)+' '+LS_ErrorConnection+' '+self.TwitterURL+'; Error code: '+IntToStr(HTTPClient.ResponseCode));
      Close(ErrorLogFile);
      self.Terminate;
      HTTPClient.Free;
      IOHandler.Free;
    end;
  finally
    HTTPClient.Free;
    IOHandler.Free;
  end;

  if Application.Terminated or self.Terminated then exit;

  S:=copy(S, pos('<b>WarframeAlerts</b></span>', S), Length(S)-pos('<b>WarframeAlerts</b></span>', S));

  while pos('tweet-timestamp js-permalink js-nav', S)>0 do begin
    S:=copy(S, pos('tweet-timestamp js-permalink js-nav', S)+35, Length(S)-pos('tweet-timestamp js-permalink js-nav', S)-35);
    S2:=copy(S, pos('data-time="', S)+11, Length(S)-pos('data-time="', S)-11);
    NewAlertDate:=StrToInt(copy(S2, 1, pos('"', S2)-1));
    S2:=copy(S, pos('<p class="js-tweet-text tweet-text">', S)+36, pos('</p>', S)-pos('<p class="js-tweet-text tweet-text">', S)-36);
    for i:= 0 to WarframeEventList.WarframeEventCount-1 do
      if (WarframeEventList.WarframeEvent[i].EventDate=NewAlertDate) and (WarframeEventList.WarframeEvent[i].Planet=copy(S2, 1, pos(')', S2))) then
        NewAlertDate:=0;

    if NewAlertDate=0 then continue;

    Inc(WarframeEventList.WarframeEventCount);
    SetLength(WarframeEventList.WarframeEvent, WarframeEventList.WarframeEventCount);

    for i:= 0 to WarframeEventList.WarframeEventCount-2 do begin
      if WarframeEventList.WarframeEvent[i].EventDate>NewAlertDate then begin
        for l:=WarframeEventList.WarframeEventCount-1 downto i+1 do
          WarframeEventList.WarframeEvent[l]:=WarframeEventList.WarframeEvent[l-1];
        Break;
      end;
    end;

    if i<=WarframeEventList.NotifyEvent.ID then Inc(WarframeEventList.NotifyEvent.ID);

    WarframeEventList.WarframeEvent[i].GUID:='';
    WarframeEventList.WarframeEvent[i].ItemIndex:=-2;
    WarframeEventList.WarframeEvent[i].EventType:=1;
    WarframeEventList.WarframeEvent[i].Planet:=copy(S2, 1, pos(')', S2));
    S2:=copy(S2, Length(WarframeEventList.WarframeEvent[i].Planet)+3, Length(S2)-Length(WarframeEventList.WarframeEvent[i].Planet));
    WarframeEventList.WarframeEvent[i].Mission:=copy(S2, 1, pos(' -', S2)-1);
    WarframeEventList.WarframeEvent[i].EventDate:=NewAlertDate;
    S2:=copy(S2, Length(WarframeEventList.WarframeEvent[i].Mission)+4, Length(S2)-Length(WarframeEventList.WarframeEvent[i].Mission));
    WarframeEventList.WarframeEvent[i].Time:=StrToInt(copy(S2, 1, pos('m', S2)-1))-1;
    S2:=copy(S2, pos('-', S2)+2, Length(S2)-pos('-', S2));
    WarframeEventList.WarframeEvent[i].RewardCredits:=StrToInt(copy(S2, 1, pos('cr', S2)-1));
    WarframeEventList.WarframeEvent[i].RewardOther:=copy(S2, pos('cr', S2)+5, Length(S2)-pos('cr', S2));
    WarframeEventList.WarframeEvent[i].RewardOtherAmount:=1;
    WarframeEventList.WarframeEvent[i].Notified:=False;
    WarframeEventList.WarframeEvent[i].Hidden:=False;
  end;

  self.Free;
end;

procedure TUpdateFromRSSThread.Execute;
var
  HTTPClient: TIdHTTP;
  IOHandler: TIdSSLIOHandlerSocketOpenSSL;
  S, S2, S3: String;
  i: Integer;
  NewEventType: Byte;
  ErrorLogFile: TextFile;
begin
  HTTPClient:=TIdHTTP.Create(nil);
  IOHandler:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    try
      HTTPClient.IOHandler:=IOHandler;
      HTTPClient.HandleRedirects:=True;
      S:=HTTPClient.Get(self.RSS_URL);
    except
      Assign(ErrorLogFile, AppPath+'Error.log');
      if not(FileExists(AppPath+'Error.log')) then
        Rewrite(ErrorLogFile);
      Append(ErrorLogFile);
      Writeln(ErrorLogFile, DateTimeToStr(Now)+' '+LS_ErrorConnection+' '+self.RSS_URL+'; Error code: '+IntToStr(HTTPClient.ResponseCode));
      Close(ErrorLogFile);
      self.Terminate;
      HTTPClient.Free;
      IOHandler.Free;
    end;
  finally
    HTTPClient.Free;
    IOHandler.Free;
  end;

  if Application.Terminated or self.Terminated then exit;

    for i:= 0 to WarframeEventList.WarframeEventCount-1 do
      if (WarframeEventList.WarframeEvent[i].EventType=2) or (WarframeEventList.WarframeEvent[i].EventType=3) then
        WarframeEventList.WarframeEvent[i].Time:=0;

  while pos('<item>', S)>0 do begin
    S:=copy(S, pos('<item>', S)+6, Length(S)-pos('<item>', S)-6);
    S2:=LowerCase(copy(S, pos('<author>', S)+8, pos('</author>', S)-pos('<author>', S)-8));
    NewEventType:=0;
    if S2='alert' then
      NewEventType:=1;
    if S2='invasion' then
      NewEventType:=2;
    if S2='outbreak' then
      NewEventType:=3;

    if NewEventType=1 then
      Continue;

    S2:=LowerCase(copy(S, pos('<guid>', S)+6, pos('</guid>', S)-pos('<guid>', S)-6));
    for i:= 0 to WarframeEventList.WarframeEventCount-1 do
      if ((WarframeEventList.WarframeEvent[i].EventType=2) or (WarframeEventList.WarframeEvent[i].EventType=3)) and (WarframeEventList.WarframeEvent[i].GUID=S2) then begin
        WarframeEventList.WarframeEvent[i].Time:=1;
        NewEventType:=255;
      end;

    if NewEventType=255 then
      Continue;

    Inc(WarframeEventList.WarframeEventCount);
    SetLength(WarframeEventList.WarframeEvent, WarframeEventList.WarframeEventCount);

    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].ItemIndex:=-2;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].GUID:=S2;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].EventType:=NewEventType;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].Time:=1;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOther:='';
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOtherAmount:=0;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardCredits:=0;
    S2:=copy(S, pos('<title>', S)+7, pos('</title>', S)-pos('<title>', S)-7);
    if NewEventType=2 then begin
      S2:=Copy(S2, 1, pos(' - ', S2)-1);
      WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOther:=S2;
      S3:=Copy(S2, 1, pos('VS.', S2)-1);
      S3:=Copy(S3, pos('(', S3), Length(S3)-pos('(', S3)+1);
      if pos('x ', S3)>0 then
        WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOtherAmount:=StrToInt(copy(S3, 2, pos('x ', S3)-2));
      if pos('K)', S3)>0 then
        WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardCredits:=StrToInt(copy(S3, 2, pos('K)', S3)-2))*1000;
      S3:=Copy(S2, pos('VS.', S2)+4, Length(S2)-pos('VS.', S2)-3);
      S3:=Copy(S3, pos('(', S3), Length(S3)-pos('(', S3)+1);
      if pos('x ', S3)>0 then
        if WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOtherAmount<StrToInt(copy(S3, 2, pos('x ', S3)-2)) then
          WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOtherAmount:=StrToInt(copy(S3, 2, pos('x ', S3)-2));
      if pos('K)', S3)>0 then
        if WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardCredits<StrToInt(copy(S3, 2, pos('K)', S3)-2))*1000 then
          WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardCredits:=StrToInt(copy(S3, 2, pos('K)', S3)-2))*1000;
    end;
    if NewEventType=3 then begin
      S2:=Copy(S2, 1, pos(' - ', S2)-1);
      WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOther:='';
      if pos('x ', S2)>0 then begin
        WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOtherAmount:=StrToInt(copy(S2, 1, pos('x ', S2)-1));
        WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardOther:=S2;
      end;
      if copy(S2, Length(S2), 1)='K' then
        WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].RewardCredits:=StrToInt(copy(S2, 1, Length(S2)-1))*1000;
      S2:=copy(S2, 1, Length(S2)-1);
    end;
    S2:=copy(S, pos('<title>', S)+7, pos('</title>', S)-pos('<title>', S)-7);
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].Planet:=Copy(S2, pos(' - ', S2)+3, Length(S2)-pos(' - ', S2));
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].Mission:=copy(S, pos('<author>', S)+8, pos('</author>', S)-pos('<author>', S)-8);
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].Notified:=False;
    WarframeEventList.WarframeEvent[WarframeEventList.WarframeEventCount-1].Hidden:=False;
  end;

  self.Free;
end;

问题是:

1)有没有办法在不破坏/重新创建线程的情况下重新使用线程?毋庸置疑,这会导致内存碎片化,并且在一天的不间断工作后程序开始膨胀。 我不问如何制作循环线程,因为它们不适用于此处。

2)何时是免费线程的正确时间?我应该在他们完成工作之前还是在我在计时器处理程序(每分钟触发一次)之前创建它们之前释放它?或者在执行完成后可能会出现线程自杀?

3)我应该

HTTPClient: TIdHTTP;
IOHandler: TIdSSLIOHandlerSocketOpenSSL;

一个外部(全局)对象,以避免每分钟破坏/重新创建它们?

4)当我尝试关闭我的应用程序时,如果线程处于活动状态,它会死锁。有没有办法终止线程,即使他们没有完成他们的工作或可能分离它们?

2 个答案:

答案 0 :(得分:5)

您在此处提出了很多问题,然后添加了大量代码。代码严重破坏。我可以看到:

  • Self.Free内部对Execute的错误调用。这完全是错的。如果你想要一个自我破坏的线程,你必须使用FreeOnTerminate
  • 如果发生异常,您可以免费HTTPClientIOHandler
  • 列表中似乎存在危险的数据竞争,你在这两个线程中发生变异。

我确信您的代码中还有许多其他错误。

但我不想进入那个。而且我不想解决你直接提出的所有四个问题。我只限于一个人。我相信处理这个以及所有其他问题都会消失。

  

有没有办法在不破坏/重新创建它们的情况下重新使用线程?

在评论中你也说:

  

如果我在例外后没有将其释放并且每分钟拨打Start该怎么办?

在我看来,这表明你有根本的误解。线程的代码只不过是一个函数调用。在TThread的情况下,该函数为Execute。线程启动时,调用Execute。当Execute返回时,该线程的使用寿命结束了。 <{1}}返回后无法重新启动。

这意味着如果您希望单个线程执行单个任务的多次重复,那么您需要在Execute方法中实现一个循环。

你还说:

  

我不问如何制作循环线程,因为它们不适用于此。

对不起,但那不对。使线程多次执行任务的方法是循环。


更一般地说,我觉得利用更高级别的并行库可以为您提供良好的服务。最好的是OTL。

如果你不想接受这个建议,那么我将如何构建你的程序:

  1. 删除计时器。
  2. 创建两个线程,让它们无限循环直到终止。这是Execute方法中的标准while not Terminated循环。
  3. 让线程在循环的每次迭代中完成它们的工作。
  4. 当他们完成工作时,他们需要等待一分钟(或多长时间)。通过等待指定超时的事件来做到这一点。
  5. 控制主线程可以知道该事件,该线程在退出时使用事件取消线程。
  6. 因此,为了退出两个线程上的主线程调用Execute,设置cancel事件,并在两个线程上调用Terminate。然后应用程序可以安全终止。
  7. 如果你像这样设计你的程序,所有其他问题就会消失。您只在程序的整个生命周期中创建两个线程。这涉及问题1,2和4.对于问题3,您将这些对象保留为局部变量。它们是线程的本地,应该是Free下的本地人。或者更好的是,Execute调用的辅助方法中的本地人。您的Execute方法非常大。

    与此分开,是列表中的数据竞争。我不能就如何解决这个问题给你详细的建议。显然需要一些序列化。

答案 1 :(得分:2)

  1. 重用线程的最简单方法是在Execute内运行一个循环(而不是仅执行一次操作然后终止),并检查while not Terminated或等待事件循环以允许干净关闭
  2. 当您不再需要它们时释放线程:您可以使用Terminate或信号来表明他们应该结束他们的工作。如果FreeOnTerminate为True,则主线程
  3. 中不需要额外的终止和清理代码
  4. 不,线程中的本地(私有)变量更好,因为线程应该有 尽可能少的外部依赖
  5. 死锁可能有很多原因,但如果你可以让它们完全独立于外部资源,那么死锁的风险就会降低。