如何正确实现在画布上绘制的线程? [英] How to correctly implement threads for drawing on a canvas?

查看:148
本文介绍了如何正确实现在画布上绘制的线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个从 TCustomControl 派生的自定义控件,该控件将覆盖 Paint 方法并绘制图形例如渐变背景,图形和形状等,最后是所有内容之上的网格。

I want to create a custom control derived from TCustomControl which I will be overriding the Paint method and drawing things such as a gradient background, graphics and shapes etc and then finally a grid over the top of it all.

我知道所有这些都可能很慢,因此进行优化我曾经考虑过要使用线程,例如,一个线程用于绘制背景,一个线程用于绘制形状以及一个线程用于绘制网格,但是我对正确地理解和正确实现它们并不太自信。

I know all this is likely to be slow, so to optimize it all I thought about using threads, eg one thread to paint the background, one thread for painting the shapes and one thread for painting the grid, but I am not too confident in understanding and implementing it all correctly.

通过反复试验并查看一些线程示例(尽管我永远找不到任何好的线程绘制示例),我设法提出了以下内容,这将是我的通用线程类:

Through trial and error and looking at some thread examples (though I could never find any good thread painting examples) I managed to come up with the following which would be my general purpose thread class:

type
  TCanvasThread = class(TThread)
  private
    FOnThreadPaint: TNotifyEvent;
    FCanvas: TCanvas;
  protected
    procedure Execute; override;
    procedure Sync;
  public
    constructor Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
    destructor Destroy; override;

    property Canvas: TCanvas read FCanvas;
  end;

constructor TCanvasThread.Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FCanvas := Canvas;
  FOnThreadPaint := OnPaint;
end;

destructor TCanvasThread.Destroy;
begin
  inherited Destroy;
end;

procedure TCanvasThread.Execute;
begin
  if Assigned(FOnThreadPaint) then
    Synchronize(Sync);
end;

procedure TCanvasThread.Sync;
begin
  FOnThreadPaint(Self);
end;

以上内容已在自定义控件中实现,如下所示:

And the above is implemented into the custom control like so:

type
  TMyControl = class(TCustomControl)
  private
    procedure OnClientPaint(Sender: TObject); // paint gradient
    procedure OnShapesPaint(Sender: TObject); // paint shapes etc
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TMyControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Width := 600;
  Height := 400;
end;

destructor TMyControl.Destroy;
begin
  inherited Destroy;
end;

procedure TMyControl.OnClientPaint(Sender: TObject);
begin
  GradientFillCanvas(TCanvasThread(Sender).Canvas, clSilver, clWhite, ClientRect, gdVertical);
end;

procedure TMyControl.OnShapesPaint(Sender: TObject);
begin
  TCanvasThread(Sender).Canvas.Rectangle(50, 50, 100, 100);
end;

procedure TMyControl.Paint;
begin
  TCanvasThread.Create(Canvas, OnClientPaint);
  TCanvasThread.Create(Canvas, OnShapesPaint);

  // implement other paint threads etc..
  // TCanvasThread.Create(Canvas, OnGridPaint);
  // ...

  // using regular canvas drawing here seems to be blocked too?
end;

通过上面的内容,我可以看到渐变色,并且可以看到绘制了一个白色矩形,但是调整控件窗口的大小时(例如,与客户端对齐时)会出现闪烁,我曾想过使用位图进行双重缓冲,但如果可能的话,只希望仅使用画布。我也无法使用 TMyControl.Paint 中的注释行突出显示的常规控件画布进行绘制。

With the above I can see the gradient painted and I can see a white rectangle shape been drawn, but there is loads of flicker when resizing the controls window (eg when aligned to client), I had thought of double buffering with a bitmap but if possible would prefer to use just the canvas only. I also can no longer draw using the regular controls canvas as highlighted by the commented line in TMyControl.Paint.

我是否在这里误解了一些基本的知识,但都实施错误呢?我读过诸如关键部分和线程池之类的东西,但是有点让人不知所措。我尝试使用Canvas.Lock和Canvas.UnLock,但是无论何时调整大小,所有内容都会闪烁,并且在 Paint 方法中创建线程之后,无法在常规画布上绘制。

Have I misunderstood something basic here and I've implemented it all wrong? I read things like critical sections and thread pools etc but it is somewhat overwhelming. I experimented with Canvas.Lock and Canvas.UnLock but everything flickers regardless when resizing and I cannot draw on the regular canvas after creating my threads in the Paint method.

所以我的问题是如何正确实现在画布上绘制的线程?代码是否全部错误,我需要重新启动并以正确的方式实现它?我现在真的迷失了方向,并感到相当困惑,我什至尝试将在 Paint 方法中创建线程的位置移到被拦截的 WM_SIZE 消息方法确实可以一定程度地减少闪烁,但不能完全减少闪烁,我担心我可能在这里错过了一些更大的东西,因此请您多加一些反馈和指导。

So my question is how do I correctly implement threads for drawing on a canvas? Is the code all wrong and I need to start again and implement it in a correct way? Im really lost off at this point and finding it rather confusing, I even tried moving where I create the threads in the Paint method to a intercepted WM_SIZE message method which did reduce the flicker somewhat but not completely, I am worried I may have missed something bigger here so would appreciate some feedback and guidance please.

谢谢。

推荐答案

1)您不能以多线程方式使用VCL。它只是不适合它。普遍存在的用于多线程的锁和锁的开销将是巨大的,其好处-对于99%的应用程序来说,是微不足道的。

1) You can not use VCL in multithreaded fashion. It just is not designed for it. The overhead for pervasive latches and locks for multithreading would be huge, and the benefit - unmeasurably infinitesimal for 99% of applications.

2)而且,请勿在以下位置使用Canvas:多线程的方式。参见以下代码:

2) And you DO NOT use your Canvas in multithreading way anyway. See this your code:

procedure TCanvasThread.Execute;
begin
  if Assigned(FOnThreadPaint) then
    Synchronize(Sync);

是什么意思?这恰恰意味着我不想做多线程,我想在单个Main VCL线程中运行所有工作。

What does it mean? It mean exactly "I do not want to do multithreading, I want to run all my work in the single Main VCL Thread".

是什么同步调用吗?阅读文档, Synchronize 的基本形式表示暂时停止该线程并以单线程方式进行工作。现在,如果您的所有后台工作人员线程都在执行阻止我,并在单个线程中完成我的工作,那么这就是它的意思。您创建线程只是为了立即停止它们,但是所有工作都将传输到单个Main VCL Thread。您只分配了很多不使用的资源。您制作了单线程应用程序,但负担很重,只能停止创建额外的线程。您还取消了可预测性,并创建了-google这个词! -比赛条件。现在,您有几个伪后台线程,但是您永远无法分辨出哪个先行,然后哪个行之有效。

What does Synchronize call do? Read the documentation, Synchronize in its basic form means "temporarily stop this thread and do its work in a single-threaded way". Now, if ALL AND EVERY of your background worker threads are doing "stop me and do my work in the single thread" - then that is what it says. You create the threads only to immediately stop them, but all the work is transmitted to the single Main VCL Thread instead. You only allocated damn lot of resources that you do not use. You made single-threaded application with lot of burden created-only-to-be-stopped extra threads. You additionally killed the predictability and created the - google the term! - race condition. You now have several "pseudo-background threads" but you never can tell which of them would work first, and which would work second.

那有什么选择呢?

3)首先,仅在没有其他选择时才使用多线程。而且,当您有100%孤立的任务块时,而不是一个共享变量。

3) first of all, only go multithreading when you have no other option left. And when you have chunks of task that's are 100% isolated, not aa single shared variable.

procedure TMyControl.Paint;
begin
  TCanvasThread.Create(Canvas, OnClientPaint);
  TCanvasThread.Create(Canvas, OnShapesPaint);

违反了规则,您将一个和相同的Canvas变量放入两个线程中。你不应该。如果您不能将线程分开以使其完全隔离-那么您很可能没有一项具有多重可读性的任务。

Rule is violated, you take one and the same Canvas variable and put it into both threads. You should not. IF you can not separate threads to be totally isolated - then you most probably don't have a task that is multi-threadable.

好吧,我也是严格地讲,在某些任务中可以共享少量变量,前提是必须对它们进行任何访问,因此任何2个以上的线程都不会立即执行。但是对于任何新手来说,经验法则就像我说的那样:100%隔离或没有多线程,甚至没有99%。

因此,通常您可以想要使用单个Canvas,那意味着您只能有一个线程在做。哎呀尝试使用一些更快的画布而不是标准的VCL画布。就像 http://graphics32.org 一样。在 Direct 2D GDI + 上还有 TCanvas 的实现-我不知道它们是否更快。或 http://torry.net/ 和其他类似目录中的其他一些2D图形库。

So, generally you can want to make use of a single Canvas and that means you only can have one thread doing it. Oookay. Try to use some FASTER canvas instead of standard VCL one. Like http://graphics32.org maybe. There were also TCanvas implementations over Direct 2D and GDI+ - I do not know if they are faster or not. Or some other 2D graphics library from http://torry.net/ and similar catalogues.

总而言之-在尝试制作缓慢的多线程应用程序之前,请花时间和精力来制作快速的单线程应用程序。

All in all - before trying to make slow multi-threading application - invest your time and effort into making fast single-threaded application.

4)有时,您确实可以将图片分成多个图层,例如Photoshop图层。这次您可以希望对其进行多线程处理。您创建了几个不同的位图,每个线程一个。您用透明色填充它们。然后,使线程将所需的部分绘制到自己的位图中。然后在主线程中,看到所有线程何时完成工作,然后在单个主线程中,将许多透明位图一个接一个地融合到目标窗体上 TPainBox 画布,然后按正确的顺序进行操作。
但即使那样,您最好还是将它们的股票 TCanvas TBitmap 放下,并使用更快的库。如果没有别的,我从来没有可靠,快速地工作过带有透明图像的VCL TBitmap ,它们并不是为实现真正的透明而设计的。

4) Sometimes you really can split your picture into layers, those like Photoshop layers. This time you can hope to multithread it. You create SEVERAL different bitmaps, one per thread. You fill them with TRANSPARENT color. Then you make your threads draw their required pieces into their own bitmaps. Then in the main thread you see when ALL the threads done their work, and then in the single main thread you fuse those many transparent bitmaps one after another upon the target form's it TPainBox canvas, and you do it in the proper order. But even then you better still drop them stock TCanvas and TBitmap and use faster libraries. If nothing else, I never had reliable and fast work of stock VCL TBitmap with transparent images, they just were not designed for true transparency. And it manifests itself by some unexpected shortcomings and glitches time and again.

5)那些线程的另一件事,除了种族,您只是无权在上面绘画在 WM_PAINT 事件之外或以 VCL 条款表示的GDI窗口,您在绘制表单时就违反了合同(或 Paint 方法(或 OnPaint )之外的任何 TWinControl )在基本 Paint 方法中调用的处理程序)。这只是违反MS Windows法律。您可以将一些数据缓存填充,计算或下载一些不可见数据偏移到后台线程中。甚至在极端情况下,甚至可以将该数据渲染到那些垄断的每线程一个临时位图中。但是只能在 Paint / OnPaint 内严格完成渲染表单本身,在画布上绘画的工作,并且可以在Paint方法退出后,不能卸载到任何正在运行的实体中。呈现的控制流程应全部在 Paint 内部,而决不在外部。因此,线程在这里不适用:在 Paint 方法之外执行时,它们没有触摸表单画布的合法权利。您必须阅读一些有关MS Windows GDI窗口和消息的教程,以及无效增加循环的工作原理。

5) Another thing with those threads, apart races, you just do not have any right to paint on the GDI windows outside of WM_PAINT event, or in VCL terms, you just violate the contract when you paint the form (or any TWinControl) outside its Paint method (or OnPaint handler called within the base Paint method). It is just the breach of the MS Windows laws. You may offset into background threads some data cache filling, calculating or downloading some invisible data. Maybe in an extreme case even rendering of that data into those monopolized one-per-thread temporary bitmaps. But rendering the form itself, painting upon its canvas - can only be done strictly WITHIN the Paint/OnPaint, and can NOT be offloaded into any entity running after the Paint method exited. The control flow of rendering should all inside the Paint, never outside. So, threads are not applicable here: being executed outside the Paint method they do not have legal right to touch your form's canvas. You have to read some tutorials on MS Windows GDI windows and messages and how the invalidation-recreation cycles work.

6)最后,找到OmniThreadingLibrary并阅读所有内容。您可以找到有关它的教程和解释。您必须得到一个简单的想法-多线程总是很昂贵的(如果按每个处理器计算,效率总是比单线程程序低),并且只能将程序的一部分提取到多线程中,而不是整个程序,并且任何作品中只有100%孤立的部分才是真正可以理解的。无论您做什么工作,彼此之间没有任何关系的部分都不会获得100%多线程。
换句话说,请阅读尽可能多的OTL教程和FAQ,以了解简单的想法:您一生中都不想使用多线程。多线程是规范的一个例外,仅在某些特定情况下才值得。如果您不确定是否需要多线程-那么就需要单线程。只有在没有正常和合法手段有效的情况下,才将多线程作为最后的机会。那只是在开玩笑,但只有一半。

6) and last thing, go find OmniThreadingLibrary and read all the tutorials and explanations you can find about it. You have to get the simple idea - multithreading always is expensive (always works less efficient than single-thread program, if calculating per-processor) and only some part of the program can be extracted into multi-threading, never a whole program, and that only 100% isolated parts of any work are truly-multithreadable. The parts of works that have any relations to one another are not getting 100% multithreaded no matter what you do. In other words, read as much of OTL tutorials and FAQs as you can to understand the simple idea: you do NOT want to multithread for most of your life. Multithreading is an exception from norm that only is worth it in some specific situations. When you are in doubt if you need multithreading or not - then you need single-threading. You only go multithreading as a last chance resort when no normal and legit mean works. That was half-joking, but only half.

这篇关于如何正确实现在画布上绘制的线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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