【问题标题】:Delphi while loop causing program to stop respondingDelphi while循环导致程序停止响应
【发布时间】:2017-11-09 23:52:44
【问题描述】:

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

unit DD04f1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TeCanvas, ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Button1: TButton;
    procedure Image1OnCreate();
    procedure ScreenRender();
    procedure OnCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  IsDone : Boolean;

implementation

{$R *.dfm}
procedure TForm1.OnCreate(Sender: TObject);
begin
  IsDone := False;
end;

procedure TForm1.Image1OnCreate ();
var
  Count:Integer;
begin
  image1.canvas.Create();
  image1.canvas.Pen.Color:=clBlack;
  image1.canvas.rectangle(0,0,640,480);
  image1.canvas.Pen.Color:=$ed630e; //bgr instead of rgb

  Count:=0;
  While (Count <> 640) do
  begin
    image1.Canvas.moveto(Count,0);
    image1.Canvas.LineTo(Count,480);
    Count:=Count+1;

  end;
end;

procedure TForm1.ScreenRender();
var
  Count : Integer;
begin
  Count:=0;
  While(Count<>640) do
  begin
    image1.Canvas.moveto(Count,0);
    image1.Canvas.LineTo(Count,480);
    Count:=Count+1;
  end;
end;

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

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

end.

【问题讨论】:

  • 您不允许处理 Windows 消息。你的代码有缺陷。使用调试器单步执行并找出原因。如所写,在循环运行时它不会响应任何内容,包括尝试移动表单、调整其大小或任何其他与 UI 相关的内容。我什至不确定它是如何获得消息以按照现在的方式停止循环。
  • 如果是我,我会删除这篇文章,重新考虑你正在尝试做的事情让你认为你需要这个循环,然后发布一个关于如何在没有这个循环的情况下做这件事的新问题循环。您在这里没有解释为什么您认为必须不断地在屏幕上绘制。我可以说,只要解决方案是 Application.ProcessMessages,你就做错了。
  • 无论如何,如果您需要“不断地”绘制图像,您应该使用OnPaint 事件或WM_PAINT 消息,并且仅在 Windows 告诉您它已准备好时才绘制你来画画。但既然你是在TImage而不是控制画布上绘画,我真的不知道你想做什么。
  • 您最好先了解普通的 Windows API 本身,因为 Delphi 的 VCL 框架高度基于它 - 包括尤其是 Windows 消息系统。长话短说,只要您运行任何类型的循环,Windows 就无法处理消息队列。消息队列负责处理用户输入和绘制到窗口。
  • @Jerry: 是的,我更专注于指出似乎是 XY 问题。这段代码设计得很糟糕,提出的问题是完全错误的。问题应该是首先询问进行连续绘图的正确方法,而不是如何解决循环问题。

标签: delphi while-loop crash pascal


【解决方案1】:
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 对象)正处于“消息风暴”的中心,它们必须及时解决问题。每秒有数百条这样的消息传入,一个窗口过程(内置于 Delphi 7 表单的 VCL 类中)应该在为时已晚之前接收、发送和处理每一条消息。

一旦您通过使事件处理程序之一变长甚至无休止来阻止该进程 - 您就破坏了操作系统和应用程序之间的基本合同。

您必须进行“控制反转”,将您的连续工作分成小块,并让 Windows 在认为合适时调用这些块。 例如尝试使用TTimer

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

跳过那里的所有多线程内容,对于您的情况,重要的是其他线程创建那些我们必须在窗体上绘制的“工作块”当 Windows 要求我们这样做时一些合理的帧率(不太快也不太慢)。你的工作块是根本不同的,所以所有与你无关的线程的东西。

渲染是在TTimer 事件中进行的。因此,设置计时器、打开和关闭它的“框架”可能会让您感兴趣。但是,您将在 .OnTimer 事件中执行的工作将大不相同(只是绘制一些东西,或者甚至只是 invalidating 表单的某些部分并等待 Windows 触发 OnPaint 事件。)。

【讨论】:

  • 我认为使用 TTimer 是我应该做的开始。感谢您的帮助!
【解决方案2】:

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

我不确定 VCL 是否是游戏的最佳基础。不同的理念和需求。正如 Arioch 所解释的 Delphi 的 VCL 是事件驱动的。事情的发生是为了响应 Windows 消息,甚至是绘画。如果没有任何事情导致需要重新粉刷,则不会重新粉刷任何东西。

这与我对游戏引擎的理解截然不同(我绝不是专家)。即使没有发生任何事情,他们也会连续不断地绘制帧,以尽可能流畅地呈现。每一帧都可能包含基于游戏规则、物理、玩家输入、动画的底层结构的更新,但即使它们保持不变,也会绘制新的帧。基本上三个步骤发生在一个简化的“游戏循环”中

  • 输入
  • 更新
  • 演示文稿

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

这是我发现很难融入 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.

【讨论】:

  • 坦率地说,有一些库可以用 Pascal 制作简单的 2D 和 3D 游戏,包括 Delphi 和 FreePascal/Lazarus。在人们证明这是可能的并且失去兴趣之后,它们中的大多数被抛弃了。但它们或多或少还是有用的。如果 topicstarter 想做一些游戏设计,也许他会从谷歌搜索这些库开始,并阅读一些关于游戏设计的通用书籍(比如矩阵微积分,用于将多个 3D 转换组合成一个动作)
  • @Arioch'如果这些库基于 VCL,我会觉得有趣。出于业余爱好,我对 DirectX 和 C# 进行了一些实验,从中学到的第一件事是,正确设计的游戏循环似乎是基础。自己这样做可以让您完全控制。带有自己的消息循环的 VCL 在这方面似乎是矛盾的。
  • 坦率地说,Windows GDI 从来都不是为动作游戏设计的,甚至在 Windows 95 之前(参见 1994 年的 16 位库:en.wikipedia.org/wiki/WinG
  • 滥用VCL/GDI(拉技术)制作动作游戏(推技术)是可能的,但并不实用。它要么是对荣耀的追求(是的,我可以让这些东西做这项工作是为了避免),要么是一些非常利基强加的必要性。比如,在 HTML 页面中有一个 DosBox 被重写为 Flash 插件,这本身就是对技术的滥用。但它提供了在现代平板电脑和计算机上玩 1990 年代 DOS 游戏的条件:-D 但是将动作游戏嵌入 GDI/VCL 的实际理由是什么?我没有看到。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多