从非主线程绘制到主窗体画布 [英] Drawing to main form canvas from a non-main thread

查看:120
本文介绍了从非主线程绘制到主窗体画布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图为我的学校项目制作街机游戏。基本思想是在除main之外的其他线程中进行所有的数学和绘图工作,并仅将主线程用于输入例程。绘图是通过保存在外部单元中的过程来完成的,通过创建位图,然后在位图上绘制部分环境并最终在主窗体的画布上绘制位图来完成。当我完成绘图程序时,我试图从主线程运行它,并设法使每一个想法都按预期工作(除了整个应用程序窗口被冻结的事实,但由于主线程没有停止工作,所以类似于这是预期的)。然后我试图把程序放到其他线程中,并停止工作(尽管调试例程报告该程序被重复执行,但它并没有绘制一个单一的东西)。在添加几个然后删除调试程序后,它开始工作,原因不明,但不可靠。在大约80%的情况下,它可以平稳运行,但在其他情况下,它会在十到三十帧后停止,有时甚至不会在最后一帧卡住的环境中拖出一些环境部分。



主窗体单元的重要部分看起来像这样

  procedure TForm1.Button1Click(Sender:TObject); 

开始
运行:=未运行;
如果运行AppTheard.Create(false);
end;

过程AppTheard.execute;

begin
form1.Button1.Caption:='running';
在运行时开始执行view.nextframe;结束;
form1.Button1.Caption:='不再运行';

end;

另一个单元中的nextframe过程看起来像这样

  Camera =类
owner:Tform;
focus:GravityAffected;
墙壁:PBlankLevel;
Creeps:MonsterList;
FrameRateCap,lastframe:integer;
背景:TBitmap;
plocha:TBitmap;
RelativePosY,RelativePosX:integer;
构造函数create(owner:Tform; focus:GravityAffected; Walls:PBlankLevel; Creeps:MonsterList; FrameRateCap:integer; background:TBitmap);
程序nextframe;
end;



程序camera.nextframe;
var i,i1,top,topinfield,left,leftinfield:integer;

程序修复
//一些不重要的数学在这里

程序vykresli(co:vec);
begin
如果co受重力影响,则
plocha.Canvas.Draw(co.PositionX * fieldsize + Gravityaffected(co).PosInFieldX-Left * fieldsize + leftinfield-co.getImgPosX,
co.PositionY * fieldsize + Gravityaffected(co).PosInFieldY-top * fieldsize + topinfield-co.getImgPosY,
co.image)
else
plocha.Canvas.Draw(co.PositionX * fieldsize-Left * fieldsize + leftinfield-co.getImgPosX,
co.PositionY * fieldsize-top * fieldsize + topinfield-co.getImgPosY,
co.image);
end;

begin
//一些更不重要的数学

vykresli(focus);

对于i:=左+ 1到左+2​​ +(plocha.Width div字段大小)// //vykreslenízdí
对于i1:=上+ 1到上+ 2 + (i< Walls.LevelSizeX)和(i1< Walls.LevelSizeY)和(i> = 0)和(i1> = 0)以及walls.IsZed(i,i1),则执行

begin vykresli(walls ^ .GiveZed(i,i1)^); end; ((gettickcount()mod high(word)) - lastframe) $ b while abs (1000 div FrameRateCap)do sleep(1);
lastframe:= gettickcount mod high(word);

owner.Canvas.Draw(-fieldsize,-fieldsize,plocha);

end;

有人可以告诉我我做错了什么?



1)所有VCL必须在主线程内完成交互



您的线程直接访问VCL控件。你不能这样做,因为VCL不是线程安全的。您必须将所有事件同步回主线程,并让主线程完成这项工作。

2)所有自定义UI绘图)必须在表单的 OnPaint 事件中完成。



这解释了为什么它有时会起作用,而不是其他时间。表单将自动绘制,如果您不使用此事件,您的自定义绘图将仅由VCL绘制。



3)所有用户界面绘图必须在主线程中完成



这使我们回到了第1点和第2点。VCL不是线程安全的。您的辅助线程只应负责执行计算,但不能绘制UI。在执行一些计算或做了很长时间的工作之后,您必须将结果同步回主线程,并让该主线程完成绘图。



4)该线程应该完全独立



你不应该在这个辅助线程中放置任何代码,它知道它将如何显示。在你的情况下,你明确地引用了表单。你的线程不应该知道它是否被一个表单使用。您的线程只应执行冗长的计算工作,并且绝对不考虑用户界面。当您需要指示重新绘制时,将事​​件同步回主表单。



结论

您需要研究线程安全性。通过这样做,您将能够回答大部分自己的问题。严格来说,只有这样才能完成这个繁重的工作,否则会导致用户界面陷入困境。不要担心太慢的用户界面,大多数现代计算机都能够在很短的时间内执行复杂的绘图。这不需要在一个单独的线程中。






编辑



经过几年的经验,我发现上述#3不一定是真的。事实上,在很多情况下,从线程内部执行详细绘制是一种很好的方法,但是主线程只负责将该图像呈现给用户。

当然,这是一个完整的话题。您需要能够安全地将在一个线程中管理的图像绘制到另一个线程。这也需要使用 Synchronize

I am trying to make an arcade game for my school project. The basic idea is to do all the math and drawing in other thread than the main, and to use the main thread only for input routines. Drawing is done by a procedure saved in an external unit, and is done by creating a bitmap, then drawing parts of the environment on the bitmap and finally dawing the bitmap on the main form's canvas. When I finished the drawing procedure, I tried to run it from the main thread, and managed to make everythink work as expected (except for the fact that the whole application window was frozen, but since the main thread was working without stopping, sommething like that was expected). Then i tried to put the procedure in other thread, and it stopped working (it didnt draw a single thing despite debug routines reporting that the procedure was repeatedly executed). After a few added and then deleted debug routnes, it started to work for no apparent reason, but not reliably. in about 80% of cases it runs smoothly, but in the rest it stops after ten to thirty frames, sometimes even not draving some of the environment parts in the last frame where it gets stuck.

The important part of the main form unit looks like this

procedure TForm1.Button1Click(Sender: TObject);

begin
running:=not running;
if running then AppTheard.Create(false);
end;

Procedure AppTheard.execute;

begin
 form1.Button1.Caption:='running';
 while running do begin view.nextframe; end;
 form1.Button1.Caption:='no longer running';

end;

and the nextframe procedure in the other unit looks like this

     Camera = class
 owner:Tform;
 focus:GravityAffected;
 Walls:PBlankLevel;
 Creeps:MonsterList;
 FrameRateCap,lastframe:integer;
 Background:TBitmap;
 plocha:TBitmap;
 RelativePosY,RelativePosX:integer;
 constructor create(owner:Tform; focus:GravityAffected; Walls:PBlankLevel; Creeps:MonsterList; FrameRateCap:integer; background:TBitmap);
 procedure nextframe;
end;    



 procedure camera.nextframe;
 var i,i1,top,topinfield, left,leftinfield: integer ;

  procedure Repair
      //some unimportant math here

  Procedure vykresli(co:vec);
   begin
   if co is gravityaffected then
    plocha.Canvas.Draw(co.PositionX*fieldsize+Gravityaffected(co).PosInFieldX-Left*fieldsize+leftinfield-co.getImgPosX,
     co.PositionY*fieldsize+Gravityaffected(co).PosInFieldY-top*fieldsize+topinfield-co.getImgPosY,
     co.image)
   else
     plocha.Canvas.Draw(co.PositionX*fieldsize-Left*fieldsize+leftinfield-co.getImgPosX,
     co.PositionY*fieldsize-top*fieldsize+topinfield-co.getImgPosY,
     co.image);
   end;

 begin
   // some more unimportant math

  vykresli(focus);                                                 

  For i:= Left+1 to left+2+(plocha.Width div fieldsize) do                                                         //vykreslení zdí
   For i1:= Top+1 to top+2+(plocha.Height div fieldsize) do
    if (i< Walls.LevelSizeX) and (i1< Walls.LevelSizeY) and (i>=0) and (i1>=0) and walls.IsZed(i,i1) then
     begin vykresli(walls^.GiveZed(i,i1)^);end;

  while abs((gettickcount() mod high(word))-lastframe) < (1000 div FrameRateCap) do sleep(1); 
  lastframe:=gettickcount mod high (word);

  owner.Canvas.Draw(-fieldsize,-fieldsize,plocha);     

 end;

Can someone please tell me what am I doing wrong?

解决方案

I see a number of things wrong in your approach at this.

1) All VCL interaction must be done from within the main thread

Your thread is directly accessing VCL controls. You cannot do this, as VCL is not thread-safe. You have to synchronize all your events back to the main thread, and let the main thread do this work.

2) All custom UI drawing (to the form) must be done from within the form's OnPaint event.

This explains why it works sometimes and not other times. The form is automatically painted, and if you don't use this event, your custom drawing will just be drawn over by the VCL.

3) All UI drawing must be done from within the main thread

This brings us back to points 1 and 2. VCL is not thread-safe. Your secondary thread should only be responsible for performing calculations, but not drawing the UI. After performing some calculation or doing some lengthy work, you must synchronize the results back to the main thread, and let that main thread do the drawing.

4) The thread should be entirely self-contained

You shouldn't put any code in this secondary thread which has any knowledge of how it will be displayed. In your case, you are explicitly referencing the form. Your thread should not even know if it's being used by a form. Your thread should only perform the lengthy calculation work, and have absolutely 0 consideration of the user interface. Synchronize events back to your main form when you need to instruct it to redraw.

Conclusion

You need to research thread safety. You will be able to answer most of your own questions by doing so. Make this thread strictly only to take care of the heavy work which would otherwise bog down the UI. Don't worry much about a slow UI, most modern computers are able to perform complex drawing in a small fraction of a second. That doesn't need to be in a separate thread.


EDIT

After a few more years of experience, I've come to realize that #3 above is not necessarily true. In fact, in many cases, it's a great approach to perform detailed drawing from within a thread, but then the main thread would only be responsible for rendering that image to the user.

That, of course, is a whole topic of its own. You need to be able to safely paint the image which is managed in one thread to the other thread. This, also, requires use of Synchronize.

这篇关于从非主线程绘制到主窗体画布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆