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) 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);
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".
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.
So what are the options then?
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);
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.
Oookay, I am too rigorous, there are tasks where some SMALL amount on variables can be shared, on the premise ANY access to them is BRILLIANTLY ARBITRATED so never ever 2+ threads would do it at once. But for any novice the thumb rule is like I said: 100% isolation or no multi-threading, not even 99%.
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) 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) 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) 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.