Steve Maughan 26 Posted April 13, 2020 I'm using the Gnostice tools to create a PDF of a map. The user can select options (e.g. paper size) and then sees a preview before clicking OK and the PDF is exported. The time to create the preview can be considerable; maybe 5 seconds for a complex map. As a result I'd like to create the preview in a separate thread. I can do this using a TTask. What I'm not clear about is what to do if the user changes the options while the preview image is being rendered. The "old" image is now obsolete. I need to destroy the TTask and then create a new one. Does anyone have any code examples of this workflow? Thanks, Steve Share this post Link to post
Anders Melander 1784 Posted April 13, 2020 You could just create a new task and ignore the old one (including its output). If the rendering can't be interrupted then there isn't much else you can do. The actual implementation is trivial and will depend on the surrounding code so there's not much point in giving examples. Share this post Link to post
Steve Maughan 26 Posted April 13, 2020 Thanks Anders — that makes sense. However, I'm concerned about memory leaks. Suppose I'm creating a TBitmap in the task. How would I guarantee: that it won't corrupt the display (overwrite the second task's bitmap); and it'll be destroyed on completion. This may well be a noob type questions as I've had little experience with TTask before. Thanks — Steve Share this post Link to post
vfbb 285 Posted April 14, 2020 About Tasks there are infinite ways to do this, try to create a bitmap for each Task, you create from outside and destroy from outside the Task. You will cancel the current Task, and without WaitFor it, create a new one with a new bitmap, when closing the form or the application you check the TPair <TBitmap, TTask> list using Task.WaitFor and then Bitmap.Free. About the performance, I will disappoint you a little. The delphi TBitmap is meant to be used only on the main thread. Although it works in threads, it has a critical section that does not allow you to paint TBitmap and process Paint forms simultaneously. But, as I said, although it doesn't take full advantage of the computer's potential, the program will work, and it probably won't harm you. Share this post Link to post
Fr0sT.Brutal 900 Posted April 14, 2020 Just disable ability to launch another render until current one is done. Share this post Link to post
Anders Melander 1784 Posted April 14, 2020 38 minutes ago, Fr0sT.Brutal said: Just disable ability to launch another render until current one is done. That doesn't really address the problem. Share this post Link to post
Anders Melander 1784 Posted April 14, 2020 10 hours ago, Steve Maughan said: However, I'm concerned about memory leaks. Suppose I'm creating a TBitmap in the task. How would I guarantee: that it won't corrupt the display (overwrite the second task's bitmap); and it'll be destroyed on completion. Okay, here's what I would do: First of all I would use TThread instead of tasks as that will give you better control of thread lifetime and resources (the bitmap). Let the thread own the bitmap. Remember to lock the thread canvas when you start and unlock it when you're finished. This prevents the VCL handle cache (controlled by the main thread) from messing with the bitmap while you're working on it. The thread also owns a windows event object. The thread signals this event when the render has completed. This allows the outside to wait or poll for for render completion. When the event has been signaled the render is complete and it's safe to access the bitmap. The tricky part is deciding when to destroy the thread. Since the thread owns the bitmap you can't have the thread destroy itself when it's done. On the other hand if you "forget" about any existing thread when a new thread is started then these old threads cannot be destroyed when they're done. If you have a good understanding of race conditions and synchronization objects then this can be solved fairly easy (but it's more complex than I have time to explain now). Otherwise I suggest you just keep a list of threads. Add new threads to the list and remove from the list and free when a thread has signaled completion. At some point you will have to go through the list and remove and free the old threads. Share this post Link to post
Fr0sT.Brutal 900 Posted April 14, 2020 8 hours ago, Anders Melander said: That doesn't really address the problem. That really does address the cause of the problem. There's no sense in giving an opportunity to launch several renders at the same time. And blocking of operation's repeat is very common in UI Share this post Link to post
Steve Maughan 26 Posted April 14, 2020 Thanks for all the input. It's clearly tough to give advice without a solid example. I've created a simple project that illustrates the problem — see attached. Here's a short video that explains what's going on: https://www.loom.com/share/500d1f2b0f1f4fe4857eb9a18e07b958 Any suggestions for how to stop the GUI freezing when multiple click to the colorlist would be appreciated. I would have thought this is a common problem. Thanks, Steve Background-Render.zip Share this post Link to post
Anders Melander 1784 Posted April 14, 2020 2 hours ago, Fr0sT.Brutal said: That really does address the cause of the problem Imagine if your browser worked that way. Page slow to load? Sorry - You'll have to wait until it loads until you can try something else. Share this post Link to post
Steve Maughan 26 Posted April 14, 2020 8 minutes ago, Anders Melander said: Imagine if your browser worked that way. Page slow to load? Sorry - You'll have to wait until it loads until you can try something else. Exactly! I would have thought this is a common problem Share this post Link to post
Steve Maughan 26 Posted April 14, 2020 @Primož Gabrijelčič you're the resident threading expert. Any idea how you'd achieve this using OmniThread library? Thanks, Steve Share this post Link to post
Anders Melander 1784 Posted April 14, 2020 16 minutes ago, Steve Maughan said: Any idea how you'd achieve this using OmniThread library? The threading library doesn't matter if you don't understand how to solve the problem and you won't learn by having other people solve it for you. It's a fairly simple problem and there's a gazzilion different ways to solve it. Why not give it a go on your own? Share this post Link to post
Attila Kovacs 629 Posted April 14, 2020 On 4/13/2020 at 9:21 PM, Steve Maughan said: I'm not clear about is what to do if the user changes the options while the preview image is being rendered "Debouncing". Slow users are out of luck. Share this post Link to post
Fr0sT.Brutal 900 Posted April 15, 2020 (edited) 10 hours ago, Anders Melander said: Imagine if your browser worked that way. Page slow to load? Sorry - You'll have to wait until it loads until you can try something else. 10 hours ago, Steve Maughan said: Here's a short video that explains what's going on OK, now that I see the interface my suggestion of blocking seems ineffective (though it would be nice in another cases). In this case: - Launch render in bg thread generating some unique value that will identify its results (ExpectResID := NewGUID; Thread.ResID := ExpectResID; Thread.Start) - Output some information like "Render in process..." on the panel that will later contain the image - If user starts another render, launch one more thread with another ResID and override ExpectResID (the value of ID to expect) so that when 1st thread finishes and reports results, they will be discarded. Optionally, if rendering algo allows cancelling, cancel the old thread to release resources. Otherwise just let the thread finish its job. - When rendering thread finishes, post this event to form async-ly with PostMessage - Paint the image - Optionally consider limitation of number of launched threads Edited April 15, 2020 by Fr0sT.Brutal 1 Share this post Link to post
Steve Maughan 26 Posted April 15, 2020 6 hours ago, Fr0sT.Brutal said: OK, now that I see the interface my suggestion of blocking seems ineffective (though it would be nice in another cases). In this case: - Launch render in bg thread generating some unique value that will identify its results (ExpectResID := NewGUID; Thread.ResID := ExpectResID; Thread.Start) - Output some information like "Render in process..." on the panel that will later contain the image - If user starts another render, launch one more thread with another ResID and override ExpectResID (the value of ID to expect) so that when 1st thread finishes and reports results, they will be discarded. Optionally, if rendering algo allows cancelling, cancel the old thread to release resources. Otherwise just let the thread finish its job. - When rendering thread finishes, post this event to form async-ly with PostMessage - Paint the image - Optionally consider limitation of number of launched threads This seems like the most obvious way to tackle the issue. I'll report back on progress. Thanks, Steve Share this post Link to post
Steve Maughan 26 Posted April 15, 2020 Done! I've attached my solution for those interested. Here's a brief explanation: Whenever the color changes I create a render thread derived from TThread The render thread has an ID (GUID string) and a color (or drawing parameters) I record the last created ID When the thread has completed rendering it posts a message The main thread processes the message, checking to see if the ID is the same as the last ID. If it is then it's draw All render threads are then destroyed Works like a charm! What was tripping me up was wanting to rely on a threading library (which I'm not that familiar with). In this case it was much simpler to create my own thread. Thanks everyone — Steve Background-Render-FINISHED.zip Share this post Link to post
Steve Maughan 26 Posted April 15, 2020 I posted the small project to GitHub in case it's of interest to others: https://github.com/stevemaughan/Background-Render Steve Share this post Link to post