我想创建一个从TCustomControl
派生的自定义控件,该控件将覆盖Paint
方法并绘制诸如渐变背景,图形和形状等内容,然后在其上方绘制网格。
我知道所有这些都可能会很慢,因此要优化所有我想使用线程的想法,例如,一个线程绘制背景,一个线程绘制形状,一个线程绘制网格,但是我对此不太自信正确理解和实施所有内容。
通过反复试验并查看一些线程示例(尽管我永远找不到任何好的线程绘制示例),我设法提出了以下内容,这将是我的通用线程类:
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;
以上是在自定义控件中实现的,如下所示:
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
中的注释行突出显示的那样。我是否在这里误解了一些基本知识,但都实施错误呢?我读过诸如关键部分和线程池之类的东西,但是有点让人不知所措。我尝试了Canvas.Lock和Canvas.UnLock,但是无论何时调整大小,所有内容都会闪烁,并且在
Paint
方法中创建线程后无法在常规画布上绘制。所以我的问题是如何正确实现在画布上绘制的线程?代码是否全部错误,我需要重新启动并以正确的方式实现它?我现在真的迷失了方向,并感到相当困惑,我什至尝试将在
Paint
方法中创建线程的位置移到被拦截的WM_SIZE
消息方法中,该方法确实减少了闪烁但未完全消除闪烁,我担心我可能在这里错过了一些更大的东西,因此,请您提供一些反馈和指导。谢谢。
最佳答案
1)您不能以多线程方式使用VCL。它只是不是为此而设计的。普遍使用的多线程锁和锁的开销将是巨大的,其好处-对于99%的应用程序来说,是无法估量的无限。
2)而且您也不要以多线程方式使用Canvas。查看此代码:
procedure TCanvasThread.Execute;
begin
if Assigned(FOnThreadPaint) then
Synchronize(Sync);
这是什么意思?这意味着“我不想执行多线程,我想在单个Main VCL线程中运行所有工作”。
Synchronize
调用有什么作用?阅读文档,Synchronize
的基本形式表示“暂时停止该线程并以单线程方式完成其工作”。现在,如果您的所有后台工作人员线程都在执行“阻止我,并在单个线程中完成我的工作”,那么这就是它的意思。您创建线程只是为了立即停止它们,但是所有工作将被传输到单个Main VCL Thread。您只分配了很多不使用的资源。您制作了单线程应用程序,但负担很重,只能停止创建额外的线程。您还取消了可预测性,并创建了-google这个词! -比赛条件。现在,您有几个“伪后台线程”,但您永远无法分辨出哪个先运行,然后再运行。那有什么选择呢?
3)首先,仅在没有其他选择时才使用多线程。并且当您有100%孤立的任务块时,而不是单个共享变量。
procedure TMyControl.Paint;
begin
TCanvasThread.Create(Canvas, OnClientPaint);
TCanvasThread.Create(Canvas, OnShapesPaint);
违反规则,您将一个和相同的Canvas变量放入两个线程中。你不应该。如果您不能将线程分开以使其完全隔离-那么您很可能没有一项具有多重可读性的任务。
太棒了,我太严格了,有些任务可以共享一些小变量,前提是必须对它们进行任何访问都是明智的,所以永远不会有2个以上的线程立即执行。但是对于任何新手来说,经验法则就像我说的那样:100%隔离或没有多线程,甚至没有99%。
因此,通常您可能希望使用单个Canvas,这意味着您只能有一个线程来做。哎呀尝试使用一些更快的画布而不是标准的VCL画布。可能像http://graphics32.org。在
TCanvas
和Direct 2D
上还有GDI+
实现-我不知道它们是否更快。或其他来自http://torry.net/和类似目录的2D图形库。总而言之,在尝试制作缓慢的多线程应用程序之前,请花时间和精力来制作快速的单线程应用程序。
4)有时,您确实可以将图片分成多个图层,例如Photoshop图层。这次您可以希望对其进行多线程处理。您创建了几个不同的位图,每个线程一个。您用透明色填充它们。然后,使线程将所需的部分绘制到自己的位图中。然后在主线程中,看到所有线程何时完成工作,然后在单个主线程中,将许多透明位图一个接一个地融合在目标表单的
TPainBox
画布上,并以正确的顺序进行。但是即使那样,您最好还是放弃它们的库存
TCanvas
和TBitmap
并使用速度更快的库。如果没有其他问题,我从来没有可靠,快速地处理带有透明图像的普通VCL TBitmap
,它们并不是为实现真正的透明而设计的。它以一些意想不到的缺点和反复出现的故障表现出来。5)关于这些线程,除了种族,您没有任何权利在
WM_PAINT
事件之外的GDI窗口上绘画,或者按照VCL
的条款,您只是在绘画表单时违反了合同(或(TWinControl
方法之外的任何Paint
)(或在基本OnPaint
方法内调用的Paint
处理程序)。这只是违反MS Windows法律。您可以将一些数据缓存填充,计算或下载一些不可见数据偏移到后台线程中。甚至在极端情况下,甚至可以将该数据渲染到那些垄断的每线程一个临时位图中。但是只能在Paint
/ OnPaint
内严格完成渲染表单本身,在其画布上绘画的操作,并且不能将其卸载到Paint方法退出后运行的任何实体中。渲染的控制流应全部放在Paint
内部,而不能在外部。因此,线程在这里不适用:在Paint
方法外部执行时,它们没有触摸表单画布的合法权利。您必须阅读有关MS Windows GDI窗口和消息的一些教程,以及无效增加循环的工作方式。6)最后,找到OmniThreadingLibrary并阅读所有有关它的教程和说明。您必须得到一个简单的想法-多线程总是昂贵的(如果按每个处理器计算,总效率要比单线程程序低),并且只能将程序的一部分提取到多线程中,而不是整个程序,并且任何作品中只有100%孤立的部分才是真正可以理解的。无论您做什么工作,彼此之间没有任何关系的部分都不会获得100%多线程。
换句话说,请阅读尽可能多的OTL教程和FAQ,以了解简单的想法:您一生中大部分时间都不想使用多线程。多线程是规范的一个例外,仅在某些特定情况下才值得。如果您不确定是否需要多线程-那么就需要单线程。只有在正常和合法手段无效的情况下,才将多线程作为最后的机会。那是在开玩笑,但只有一半。
关于delphi - 如何正确实现在 Canvas 上绘制的线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44161348/