Delphi while循环导致程序停止响应

时间:2017-11-09 23:52:44

标签: delphi while-loop crash pascal

我正在使用Delphi 7,我正在编写的程序需要不断在屏幕上绘图。虽然它目前没有任何重要的东西,但这在以后的程序中是必需的。但是,当我将屏幕绘制的过程放在while循环中时,只能通过按任何按钮来停止,程序会完全停止响应。我不明白为什么会这样。当然,由于while循环可以退出,程序应该继续正常运行。 这是源代码:

clojure.core.async/alts!

2 个答案:

答案 0 :(得分:2)

procedure TForm1.OnCreate(Sender: TObject);
begin
  IsDone := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    Image1OnCreate();
    Button1.Visible := False;
    While(IsDone = False) do
    begin
      ScreenRender();
    end;
end;

假设IsDone始终为False(因为否则我们不会进入循环),此循环无法终止。这是无限的。

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  IsDone := True;
end;

您不从TForm1.Button1Click循环内部调用此过程,因此在您进入该循环后永远不会调用它。由于您永远不会退出TForm1.Button1Click过程,因此您不允许任何外部代理(如VCL中的消息调度循环)执行并调用该过程。要在进入循环后对其进行总结,就不会有任何可以更改IsDone值的可执行代码。所以,它没有改变。

事件处理程序应该是非常短的程序,几乎立即执行,并放弃执行流程控制"回到VCL内部。每一次漫长(更无限)的处理都会导致程序变得无法响应。无论Windows可能想要告诉程序多少新闻 - 程序都不会要求它们。

曾经有人告诉过,Windows窗口(GDI对象)生活在"消息风暴的中心。他们必须及时解决问题。每秒都有数百条消息传入,并且一个Window Procedure(在Delphi 7表单的VCL类中构建)应该在它们为时已晚之前接收,发送和处理它们。

一旦你通过使一个事件处理程序长期甚至无休止地阻止了这个过程 - 你就破坏了操作系统和应用程序之间的基本合同。

你必须做"反转控制",将你的连续工作分成小的短块,并在适当的时候让Windows调用那些块。 例如,尝试使用TTimer

PS。你可以看一个非常遥远的问题:

在那里略过所有多线程的东西,对于你的情况,只有其他线程创建那些"大块的工作"当Windows要求我们以合理的帧速率(不太快且不太慢)时,我们必须在我们的表单上绘制 。你的工作块根本不同,所以所有的线程都与你无关。

渲染是在TTimer个事件中进行的。所以"框架"设置计时器,打开和关闭它可能会引起你的兴趣。但是,您在.OnTimer事件中要做的工作会有很大的不同(只是绘制一些内容,甚至只是invalidating表单的某些部分并等待Windows触发OnPaint事件)。

答案 1 :(得分:2)

你已经得到了一个很好的答案,为什么你当前的代码不起作用,在你的评论中你提到你想从玩家的角度进行射线投射和绘图,所以我假设某种游戏背景。

我不确定VCL是游戏的最佳基础。不同的哲学和需求。作为Arioch'解释说Delphi的VCL是事件驱动的。事情发生在窗户消息的响应中,甚至是绘画。如果没有任何东西需要重新绘制,则不会重新绘制任何内容。

这与我理解游戏引擎的方式非常不同(我绝不是专家)。即使没有任何反复发生,它们也会不断地逐帧绘制,以尽可能呈现流畅。每个帧可能包括基于游戏规则,物理,玩家输入,动画的基础结构的更新,但即使它们保持不变,也将绘制新的帧。基本上在简化的“游戏循环”中会发生三个步骤

  • 输入
  • 更新
  • 演示

所有这一切都发生在每一帧。可能没有输入,没有游戏结构的更新,甚至不需要演示。但是所有三个步骤都属于一起,导致稍后呈现的更新的输入发生在与结果图形完全相同的帧中。

这是我觉得很难融入VCL的东西。作为解决方案必须基于现有的VCL循环和Windows消息。你基本上试图在VCL中创建这样的游戏循环。

解决您的直接问题的方法 - 您希望基于计算呈现某些内容 - 可能只是使用VCL的原则。你想要绘制一些东西。 VCL控件通常表达他们希望由Invalidate绘制的愿望,导致他们的BoundsRect无效。完成计算后,您可以这样做。在下面的例子中,我将使用一个计时器来模拟你的计算完成。请注意,Invalidate将导致为控件生成WM_PAINT消息,但不会立即重新绘制。在处理WM_PAINT之前,可能会有消息排队。 我正在使用TPaintBoxOnPaint来实际进行绘画工作,您可能希望将来在项目进展时拥有自己的控制权。

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

type
  TFormMain = class(TForm)

    procedure FormCreate(Sender: TObject);
  private
    Timer1: TTimer;
    PaintBox1: TPaintBox;
    { Private declarations }
    procedure PaintBox1Paint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

procedure TFormMain.FormCreate(Sender: TObject);
begin
  PaintBox1 := TPaintBox.Create(Self);
  PaintBox1.Parent := Self;
  PaintBox1.Align := alClient;
  PaintBox1.OnPaint := PaintBox1Paint;

  Timer1 := TTimer.Create(Self);
  Timer1.Interval := 100;
  Timer1.OnTimer := Timer1Timer;
  Randomize;
end;

procedure TFormMain.PaintBox1Paint(Sender: TObject);
var
  AColor: TColor;
  I: Integer;
begin
  for I := 0 to PaintBox1.ClientWidth - 1 do
  begin
    AColor := RGB(Random(256), Random(256), Random(256));
    PaintBox1.Canvas.Pen.Color := AColor;
    PaintBox1.Canvas.MoveTo(I, 0);
    PaintBox1.Canvas.LineTo(I, PaintBox1.ClientHeight);
  end;
end;

procedure TFormMain.Timer1Timer(Sender: TObject);
begin
  PaintBox1.Invalidate;
end;

end.