【问题标题】:Create TTimer in loop循环创建 TTimer
【发布时间】:2017-01-14 22:09:16
【问题描述】:

如何用漂亮的循环替换我的菜鸟代码? 我在 form1 上有 32 个计时器,每个计时器每 1 秒运行一次并执行 bat 文件,然后等到 bat 文件完成他的工作并再次运行计时器。

这是图片中的代码

procedure TForm1.Timer1Timer(Sender: TObject);
var nr:string;
begin
  nr := '1';
  Timer1.Enabled := False;

  if g_stop=False then
  begin
    if FileExists('test'+nr+'.bat') then
    begin
      ExeAndWait(ExtractFilePath(Application.ExeName) + 'test'+nr+'.bat', SW_SHOWNORMAL);
    end;
    Timer1.Enabled := True;
  end;
end;



procedure TForm1.Timer2Timer(Sender: TObject);
var nr:string;
begin
  nr := '2';
  Timer2.Enabled := False;

  if g_stop=False then
  begin
    if FileExists('test'+nr+'.bat') then
    begin
      ExeAndWait(ExtractFilePath(Application.ExeName) + 'test'+nr+'.bat', SW_SHOWNORMAL);
    end;
    Timer2.Enabled := True;
  end;
end;




procedure TForm1.Timer3Timer(Sender: TObject);
var nr:string;
begin
  nr := '3';
  Timer3.Enabled := False;

  if g_stop=False then
  begin
    if FileExists('test'+nr+'.bat') then
    begin
      ExeAndWait(ExtractFilePath(Application.ExeName) + 'test'+nr+'.bat', SW_SHOWNORMAL);
    end;
    Timer3.Enabled := True;
  end;
end;

【问题讨论】:

  • 当然都必须并行工作
  • 您的代码图像在这里绝对没用。请参阅this Meta post,了解代码图像不可接受的众多原因列表。此外,您的表单图像也无用;它对这个问题没有任何好处(除非你只是想炫耀你如何整齐地排列所有这些 TTimer 组件)。
  • 您要解决什么问题。这不太可能是解决方案。
  • 请注意您的代码不是并行运行的,定时器事件是在主线程中触发的。
  • 我很好奇批处理文件的作用......将它们的功能直接重写到应用程序本身可能更有意义。或者别的什么,也许。

标签: delphi


【解决方案1】:

使用计时器不是这里的方法,因为您想在后台运行冗长的操作。按照您目前的方式,如果批处理文件需要一些时间,运行批处理文件将阻止您的用户 ui。

更好的方法:使用显式线程。创建您自己的线程类作为 TThread 的后代。您的类的每个实例都有一个特定的文件名,它将负责连续运行。

unit BatchExecutionThread;

interface
uses Classes;

type

TBatchExecutionThread = class (TThread)
  private
    pBatchFileToExecute: string;
  public
    constructor Create(ABatchFileToExecute: string);

    procedure Execute; override;
end;

implementation

uses SysUtils, Windows;

constructor TBatchExecutionThread.Create(ABatchFileToExecute: string);
begin
  inherited Create;

  pBatchFileToExecute := ABatchFileToExecute;
end;

procedure TBatchExecutionThread.Execute;
begin
   { While no stop requested }
   while(not Terminated) do
   begin
      try
        { Execute Batch file if it exists }
        if FileExists(pBatchFileToExecute) then
        begin
          ExeAndWait(pBatchFileToExecute, SW_SHOWNORMAL);
        end;
      except
        { Ignore exception }
      end;
      { Wait a second }
      Sleep(1000);
   end;
end;

end.

然后,从您的应用程序中,您可以为要运行的每个文件创建该线程类的大量不同实例。

例如这样:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, BatchExecutionThread, System.Generics.Collections;

type
  TForm1 = class(TForm)
    ButtonStart: TButton;
    ButtonStop: TButton;
    procedure ButtonStartClick(Sender: TObject);
    procedure ButtonStopClick(Sender: TObject);
  private
    pThreads: TList<TBatchExecutionThread>;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ButtonStartClick(Sender: TObject);
var
  i: Integer;
  FileName: string;
  Thread: TBatchExecutionThread;
begin
  if(pThreads = nil) then
  begin
    { Create a list where we store the running threads }
    pThreads := TList<TBatchExecutionThread>.Create;

    { Create 10 threads with the batch file names from 1 to 10 }
    for i:= 1 to 10 do
    begin
      { Build the filename }
      FileName := ExtractFilePath(Application.ExeName) + 'test'+ i.ToString() +'.bat';

      { Create a thread for this file }
      Thread :=  TBatchExecutionThread.Create(FileName);
      Thread.FreeOnTerminate := true;

      { Add the thread to the list }
      pThreads.Add(Thread);

      { Start the thread }
      Thread.Start();
    end;
  end;
  { else Already started }
end;

procedure TForm1.ButtonStopClick(Sender: TObject);
var
 Thread: TBatchExecutionThread;
begin
  if(pThreads <> nil) then
  begin
    { Tell all threads to stop }
    for Thread in pThreads do
    begin
      Thread.Terminate;
    end;

    { Delete list of threads }
    FreeAndNil(pThreads);
  end;
  { else not started yet }
end;

end.

【讨论】:

  • 很有用,但如果有人想重复使用它会有一些问题。提出的问题客观上很差,不能归类为“样式偏好”,但代码确实有效:1)不要笼统地吞下、隐藏和忽略异常。鸵鸟编程很难支持和维护。 2) 记下所使用的类已经提供了什么并且不重新实现现有功能:停止请求机制重复了TThread.Terminate();Terminated。 3) 与 1 相关的一个丢失的批处理文件有一个线程静静地每秒什么都不做 - 需要以某种方式向用户报告错误。
  • 4) 将列表限制为特定类型的线程没有任何好处。将线程添加到列表后,对 TBatchExecutionThread 没有依赖关系。所以不妨允许列表保存任何TThread
  • PS:FreeOnTerminate := True;失踪了吗?
  • 虽然这个解决方案是一个好的开始,但它有点毫无意义。我会将线程数限制为 CPU 中的内核数并相应地分配任务......
  • 我想说TThread 方法在这里添加了很多样板。对于较旧的 Delphi,最好只使用 AsyncCall,对于最近的 Delphi,最好使用 TThread.CreateAnonymousThread
【解决方案2】:

已接受的答案已经概述了计时器不是您问题的解决方案,您应该改用线程。但是,我认为您发布的代码存在更根本的问题。

软件工程的一个重要原则称为DRY,是Don't repeat yourself的缩写。每当你发现自己一遍又一遍地做同样的事情时,很可能你做错了。但是你已经在正确的轨道上建议使用循环而不是单独创建 32 个计时器。

看起来您为每个计时器编写了一个单独的过程,重复相同的代码,但差异很小。相反,您只想编写一次过程,并将其分配给所有计时器。您需要做的唯一调整是根据代码确定文件名的编号。

procedure TForm1.TimerTimer(Sender: TObject);
  var FileName: string;
begin
  FileName := ExtractFilePath(Application.ExeName) + 'test' + String(TTimer(Sender).Name).Replace('Timer', '') + '.bat';
  TTimer(Sender).Enabled := False;

  if (not g_stop) then
  begin
    if FileExists(FileName) then
    begin
      ExeAndWait(FileName, SW_SHOWNORMAL);
    end;
    TTimer(Sender).Enabled := True;
  end;
end;

请注意,我还将文件名保存到一个变量中,而不是像在您的代码中那样构造它两次。这是同样的原则——你不想做两次。想象一下,您想更改文件的命名方式 - 对于原始版本,您必须在 64 个位置调整代码(每个计时器两次),现在您只需要进行一次调整。

同样,您可以从代码循环创建所有计时器。再次注意,这可能不是解决您的问题的好方法,但它是一个很好的练习。

procedure TForm1.CreateTimers;
  var i: integer;
      Timer: TTimer;
begin
  for i := 1 to 32 do
  begin
    Timer := TTimer.Create(self);
    Timer.Interval := 1000;
    Timer.Name := 'Timer' + i.ToString;
    Timer.OnTimer := TimerTimer;
  end;
end;

【讨论】:

    猜你喜欢
    • 2018-09-13
    • 2021-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-08
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多