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

Son*_*dit -1 delphi multithreading deadlock reusability

我正在使用Delphi XE2,我的应用程序用于通知twitter/rss中的新记录.在我的应用程序中,我使用2个线程每秒从twitter和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;
Run Code Online (Sandbox Code Playgroud)

var section(模块)

  WarframeEventList: TWarframeEventList;
Run Code Online (Sandbox Code Playgroud)

实施部分:

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;
Run Code Online (Sandbox Code Playgroud)

问题是:

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

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

3)我应该做

HTTPClient: TIdHTTP;
IOHandler: TIdSSLIOHandlerSocketOpenSSL;
Run Code Online (Sandbox Code Playgroud)

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

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

Dav*_*nan 5

你在这里问过很多问题,然后添加了大量的代码.代码严重破坏.我可以看到:

  • Self.Free来自内部的错误呼叫Execute.这完全是错的.FreeOnTerminate如果你想要一个自毁的线程,你必须使用.
  • 你有双自由HTTPClientIOHandler异常的情况.
  • 列表中似乎存在危险的数据竞争,您在这两个线程中发生变异.

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

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

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

在评论中你也说:

如果我在异常后没有释放并且Start每分钟拨打电话怎么办?

在我看来,这表明你有根本的误解.线程的代码只不过是一个函数调用.在该TThread功能的情况下Execute.当线程启动时,Execute被调用.当Execute返回时,该线程的使用寿命结束.一旦Execute返回就无法重启.

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

你还说:

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

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


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

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

  1. 删除计时器.
  2. 创建两个线程,让它们无限循环,直到终止.这是while not TerminatedExecute方法的标准循环.
  3. 让线程在循环的每次迭代中完成它们的工作.
  4. 当他们完成工作时,他们需要等待一分钟(或多长时间).通过等待指定超时的事件来做到这一点.
  5. 该事件可以为控制主线程所知,该线程在退出时使用该事件来取消线程.
  6. 因此,为了退出Terminate两个线程上的主线程调用,设置cancel事件,并调用Free两个线程.然后应用程序可以安全终止.

如果你像这样设计你的程序,所有其他问题就会消失.您只在程序的整个生命周期中创建两个线程.这涉及问题1,2和4.对于问题3,您将这些对象保留为局部变量.它们是线程的本地,应该是本地人Execute.或者更好的是,本地人在Execute调用的辅助方法中.你的Execute方法非常大.

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