【问题标题】:How to make a Mutlithreded idhttp calls to do work on a StringList如何使 Mutlithreded idhttp 调用在 StringList 上工作
【发布时间】:2016-08-23 08:46:32
【问题描述】:

我是线程新手,我有一个包含字符串的列表。我的目标是让多个线程对这个列表起作用,这个代码只用于一个线程,因为我目前正在学习,但是当我按下开始按钮时我得到了 AV。

type
  TDemoThread = class(TThread)
  private
    procedure Abort;
  protected
    procedure Execute; override;
  public
    List: TStringList;
  end;

procedure TfrmMain.StartButton1Click(Sender: TObject);
var
  i: integer;
  List: Tstrings;
begin
  for i := 0 to memo1.Lines.Count - 1 do
  begin
    List := TStringList.Create;
    List.Add(memo1.Lines.Strings[i]);
  end;

  Thread := TDemoThread.Create(True);
  Thread.FreeOnTerminate := True;
  Thread.Start;
end;

procedure TDemoThread.Execute;
var
  lHTTP: TIdHTTP;
  i: integer;
  X: Tstrings;
begin
  inherited;
  if Terminated then
    Exit;

  lHTTP := TIdHTTP.Create(nil);
  X := TStringList.Create;
  lHTTP.ReadTimeout := 30000;
  lHTTP.HandleRedirects := True;

  for i := 0 to List.Count - 1 do
    try
      X.Text := lHTTP.Get('https://instagram.com/' + List.Strings[i]);
      S := ExtractDelimitedString(X.Text);
      X.Clear;
      TThread.Synchronize(nil,
        procedure
        begin
          frmMain.Memo2.Lines.Add(List.Strings[i] + ' : ' + S);
        end);
    finally
    end;
end;

【问题讨论】:

  • 建议:除非您确实需要使用它提供的所有扩展功能,否则不要使用 TStringList。因为该功能使其变慢。如果您只需要一个愚蠢的累加器 - 然后使用更快的TList<string> - 或者可能是TThreadList<string>。然后在您的表单上使用TTimer 以每秒 2 或 3 次将您的备忘录赶上该列表。 /// 另外,如果您想在多线程上投入更多时间,我建议您查看第三方库 - otl.17slon.com/tutorials.htm。请参阅 Parrallel-For 和 ForJoin 原语以了解您的任务

标签: multithreading http delphi delphi-xe8


【解决方案1】:

您的问题是您从未分配给线程类的List 成员:

type
  TDemoThread = class(TThread)
  private
    procedure Abort;
  protected
    procedure Execute; override;
  public
    List: TStringList; <-- never assigned to, hence always nil
  end;

因此访问冲突。

您似乎正试图将memo1 的内容传递给线程。我会这样做:

type
  TDemoThread = class(TThread)
  private
    FData: TStringList;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TDemoThread.Create(Data: TStrings);
begin
  inherited Create(False);
  FData := TStringList.Create;
  FData.Assign(Data);
  FreeOnTerminate := True;
end;

destructor TDemoThread.Destroy;
begin
  FData.Free;
  inherited;
end;

procedure TDemoThread.Execute;
var
  lHTTP: TIdHTTP;
  i: integer;
  X: TStrings;
begin
  inherited;
  if Terminated then
    Exit;

  lHTTP := TIdHTTP.Create(nil);
  X := TStringList.Create;
  lHTTP.ReadTimeout := 30000;
  lHTTP.HandleRedirects := True;

  for i := 0 to FData.Count - 1 do
    try
      X.Text := lHTTP.Get('https://instagram.com/' + FData[i]);
      S := ExtractDelimitedString(X.Text);
      X.Clear;
      TThread.Synchronize(nil,
        procedure
        begin
          frmMain.Memo2.Lines.Add(FData[i] + ' : ' + S);
        end);
    finally
    end;
end;

procedure TfrmMain.StartButton1Click(Sender: TObject);
begin
  TDemoThread.Create(memo1.Lines);
end;

创建挂起然后立即启动是没有意义的。也不允许在 FreeOnTerminate 线程启动后对其进行引用,因此我将其删除。

TDemoThread.Execute 中的代码会泄漏,除非您专门在 ARC 平台上运行。尝试/最终是毫无意义的。你不需要一个字符串列表来保存一个string。假设您没有使用 ARC,它应该是:

procedure TDemoThread.Execute;
var
  lHTTP: TIdHTTP;
  i: integer;
  S: string;
begin
  if Terminated then
    Exit;

  lHTTP := TIdHTTP.Create(nil);
  try
    lHTTP.ReadTimeout := 30000;
    lHTTP.HandleRedirects := True;

    for i := 0 to FData.Count - 1 do
    begin
      S := ExtractDelimitedString(lHTTP.Get('https://instagram.com/' + FData[i]));
      TThread.Synchronize(nil,
        procedure
        begin
          frmMain.Memo2.Lines.Add(FData[i] + ' : ' + S);
        end);
    end;
  finally
    lHTTP.Free;
  end;
end;

【讨论】:

  • 谢谢,第一个问题解决了,现在当我按下开始时,我在 Memo2 中只得到一个结果,然后它停止并且在 EventsLog 中退出线程。
  • 我不打算为您调试整个程序(我看不到)。我回答了你问的问题。实际上还有很多。我是否可以建议您从这个问题继续并继续调试您的代码。
【解决方案2】:

我个人会避免从线程本身更新表单。线程在这里是数据生成器,而不是 GUI 管理器。所以让他们分开他们的关注点。

我会让所有线程将结果累积到同一个共享容器中,然后创建一个 GUI 线程来轮询该容器。人眼很慢,Windows GUI 也很慢,因此您的 GUI 更新频率不应超过每秒 2 或 3 次。它只会浪费 CPU 负载并使表单变得不可读。

另一件事是避免使用慢速TStringList,除非需要它的额外功能(这使它变慢)。常规的TList&lt;string&gt; 作为一个哑容器绰绰有余,而且速度更快。

type 
  TDemoThread = class;

  TfrmMain = class(TForm)
  private
    Fetchers: TThreadList<TDemoThread>;
    Data:     TThreadList<string>;

    property inProcess: Boolean read ... write SetInProcess;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  ....
  end;

  // this demo makes each thread per each line - that is actually a bad design
  // one better use a thread pool working over the same queue and only have
  // 20-40 worker threads for all the URLs
  TDemoThread = class(TThread)
  private
    URL: string;  
    List: TThreadList<string>;
    Tracker: TThreadList<TDemoThread>;
  protected
    procedure Execute; override;
  end;

procedure TfrmMain.BeforeDestruction;
begin
  while TThreadList.Count > 0 do
    Sleep(100);

  FreeAndNil( Fetchers );
  Data.Free;

  inherited;
end;

procedure TfrmMain.AfterConstruction;
begin
  Fetchers := TThreadList<TDemoThread>.Create;
  Data :=     TThreadList<string>.Create; 
  inherited;
end;

procedure TfrmMain.StartButton1Click(Sender: TObject);
var
  i: integer;
  List: Tstrings;
  worker: TDemoThread;
  URL: string;
begin
  If inProcess then exit;

  for URL in memo1.Lines do begin
    worker := TDemoThread.Create(True);  
    worker.FreeOnTerminate := True;
    worker.URL := URL;
    worker.List := Data;
    worker.Tracker := Fetchers;
    Fetchers.Add( worker );
  end;

  InProcess := True;

  for worker in Fetchers do
    worker.Start;
end;

procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
  if Value = InProcess then exit; // form already is in this mode

  FInProcess := Value;

  memo1.ReadOnly := Value;
  StartButton.Enabled := not Value;
  if Value then begin
     Memo2.Lines.Clear;
     Data.Clear;
  end;

  Timer1.Delay := 500; // twice per second
  Timer1.Enabled := Value;

  If not Value then  // for future optimisation - make immediate mode change 
     FlushData;      // when last worker thread quits, no waiting for timer event

  If not Value then
     ShowMessage('Work complete');
end;

procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
  FlushData;

  if Fetchers.Count <= 0 then
     InProcess := False;
end;

procedure TfrmMain.FlushData;
begin
  Data.LockList;  // next two operations should go as non-interruptible atom
  try
    Memo2.Lines.AddStrings( Data.ToArray() );
    Data.Clear;
  finally
    Data.UnLockList;
  end;
end;

procedure TDemoThread.Execute;
var
  lHTTP: TIdHTTP;
begin
  try 
    lHTTP := TIdHTTP.Create(nil);
    try
      lHTTP.ReadTimeout := 30000;
      lHTTP.HandleRedirects := True;

      S := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );

      List.Add( S );
    finally
      lHTTP.Destroy;
    end;
  finally
    Tracker.Remove( Self );
  end;
end;

PS。就个人而言,我也会使用 OmniThreads Library,因为它通常使维护数据生成线程更容易。例如,仅管理您创建了多少线程就变成了设置一个属性并确定所有线程何时完成它们的工作是另一个单行器。您真的不应该创建一千个线程来获取所有 URL,而是应该在 Thread Pool 中有 10-20 个线程,它们将从 Input Queue 获取 URL 并一个接一个地获取它们。我建议您在http://otl.17slon.com/tutorials.htm 阅读有关OTL 的Parallel For 和Fork-Join 模式的信息——这将使这样的应用程序更简洁、更易于编写。 Pipeline 模式可能更适合此任务 - 因为无论如何您都准备 URL 列表作为源集合。 StartButtonClick 中的一半脚手架将消失,整个 TDemoThread 类也将消失。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 2021-03-01
  • 2022-08-18
  • 1970-01-01
  • 2012-01-09
相关资源
最近更新 更多