domus 1 Posted December 31, 2024 (edited) Hi, I'm using TBitmap.LoadFromStream in a background thread (TTask.Run), but it appears to be blocking the main thread (and UI), since I see pauses in execution when this function is executing. It's loading a PNG into a TBitmap. There is absolutely no interaction needed with the UI. The bitmap is not linked to any component. If this function needs to synchronize with the main thread for a part of its algorithm, is there an alternative one available that doesn't? Thx! Edited December 31, 2024 by domus Share this post Link to post
Olli73 5 Posted December 31, 2024 8 hours ago, domus said: is there an alternative one available that doesn't? Try Bitmap32 from https://github.com/graphics32 TBitmap is not Threadsafe in most cases. Share this post Link to post
DelphiUdIT 190 Posted December 31, 2024 (edited) TBitMap is Thread Safe since Tokyo release: https://docwiki.embarcadero.com/RADStudio/Tokyo/en/Multi-Threading_for_TBitmap,_TCanvas,_and_TContext3D Of course this doesn't means that it doesnt'use main thread in sync way. Edited December 31, 2024 by DelphiUdIT Share this post Link to post
domus 1 Posted December 31, 2024 (edited) 1 hour ago, Olli73 said: Try Bitmap32 from https://github.com/graphics32 TBitmap is not Threadsafe in most cases. Does Graphics32 support FMX? Just stumbled upon Image32. Anyone know how it handles TBitmap streaming...? Edited December 31, 2024 by domus Share this post Link to post
domus 1 Posted December 31, 2024 Just now, DelphiUdIT said: Of course this doesn't means that it doesnt'use main thread in sync way. Indeed, that's what I'm trying to find out. If it does, it kind of defeats the purpose of having it run in the background. Share this post Link to post
vfbb 285 Posted December 31, 2024 @domus It seems to be specific to the D2D implementation (as the TBitmap.LoadFromStream indirectly uses map/unmap): On this case, you can switch to another render, like Skia. (Just right click on your app > Enable Skia). Although TBitmap.LoadFromStream does not use TCanvas, it is worth remembering that another UI block occurs when using the TCanvas of the bitmap in the background thread (as reported in the docwiki posted earlier). In this case, using Skia, you can add `GlobalSkiaBitmapsInParallel := True;` on your dpr to avoid this. Share this post Link to post
domus 1 Posted December 31, 2024 Just now, vfbb said: On this case, you can switch to another render, like Skia. (Just right click on your app > Enable Skia). After quite a bit of back and forth with the o1 GPT model, it suggested using TBitmapSurface directly, like TBitmapCodecManager.LoadFromStream(strm, Surf) (where strm is the PNG stream), which it claims uses only the CPU, steering clear from any blocking caused by GPU access, but having tried that, I see no difference. Still on Delphi 11, but I could try Skia anyway. It's just that the last time I tried it, a while back, it seemed slower than the native FMX library (using D2D). Thanks! Share this post Link to post
domus 1 Posted Friday at 01:51 AM My goodness. The thing that was blocking everything was a TThreadedQueue. Days of debugging, just to find out this thing is absolutely unreliable inside a TTask (in the sense that it absolutely blocks the main thread when popping an item). Share this post Link to post
Remy Lebeau 1442 Posted Friday at 03:00 AM 1 hour ago, domus said: My goodness. The thing that was blocking everything was a TThreadedQueue. Days of debugging, just to find out this thing is absolutely unreliable inside a TTask (in the sense that it absolutely blocks the main thread when popping an item). I very much doubt that. You are probably just using it incorrectly. Can you provide some code? 1 Share this post Link to post
domus 1 Posted Friday at 11:34 AM 8 hours ago, Remy Lebeau said: You are probably just using it incorrectly. Hello, Remy. You're absolutely right. I was assuming TThreadedQueue had dynamic size, like TQueue, but it's a fixed-capicity ring buffer, apparently. As an aside, do you know why its QueueSize property is returning the amount of items in the queue, contrary to the documentation, stating it returns the fixed size of the queue? (not that it matters, since I can keep the fixed size in a separate parameter, but it's misleading) Also, why are they using PushItem instead of Enqueue and PopItem instead of Dequeue? And, as a final question, why isn't there an option to make it grow dynamically? None of these are questions you're supposed to answer, of course, but just wondering... Thanks for making me dig deeper into this. Cheers, Dom Share this post Link to post
Remy Lebeau 1442 Posted Friday at 08:39 PM 8 hours ago, domus said: I was assuming TThreadedQueue had dynamic size, like TQueue, but it's a fixed-capicity ring buffer, apparently. TThreadedQueue has a public Grow() method to increase the queue size. What I find odd, though, is that Grow() is not called automatically by PushItem(). Instead, if the queue is full then PushItem() simply waits (up to the specified PushTimeout at creation) for outside code to call either PopItem() or Grow() to make room for the new item. So, in that regard, I suppose it acts as a fixed-capacity buffer. But the capacity can be changed dynamically nonetheless. 8 hours ago, domus said: As an aside, do you know why its QueueSize property is returning the amount of items in the queue Because it really does. 8 hours ago, domus said: contrary to the documentation, stating it returns the fixed size of the queue? Then the documentation is wrong. The implementation code tells a different story. 8 hours ago, domus said: Also, why are they using PushItem instead of Enqueue and PopItem instead of Dequeue? 🤷♂️ I don't know. 8 hours ago, domus said: And, as a final question, why isn't there an option to make it grow dynamically? There is - the public Grow() method. Share this post Link to post
domus 1 Posted yesterday at 12:29 AM 3 hours ago, Remy Lebeau said: There is - the public Grow() method. I wrote "dynamically," by meant "automatically." My bad. When I tracked QueueSize when starting the debugging, I saw it grow along with the pushing of items, and by having made the mistake of believing the documentation, I thought the queue's size was growing automatically (along with the items being pushed) and dismissed it as being a possible cause. So I went down all the wrong rabbit holes. I know about Grow. I assumed, at first, that it entailed the reserved space for the queue before it automatically grew along with the pushed items, analogous to TList's Capacity property. But it doesn't grow automatically, of course, just to make life a tad more exciting. Cheers! Dom Share this post Link to post
Brian Evans 109 Posted yesterday at 02:39 AM (edited) In general an ever growing queue is a bad thing - at some point adding new items has to at least pause or it grows unbounded. The default size in the .create is a bit on the low side at 10 which I think is a source of bugs. This parameter perhaps should not have a default forcing the developer to think of a sane value or the need to .grow(). The defaults for create(): AQueueDepth is the length of the queue, which is by default set to 10. PushTimeout is the timeout when a new element is pushed, which is by default set to INFINITE. PopTimeout is the timeout when a new element is popped, which is by default set to INFINITE. Edited yesterday at 02:40 AM by Brian Evans Share this post Link to post
domus 1 Posted 16 hours ago Imagine that meeting. Let's implement: TList: indefinite growth allowed; TStack: indefinite growth allowed; TQueue: indefinite growth allowed; TQueue<T>: indefinite growth allowed, but I propose we use "Push" and "Pop" instead of "Enqueue" and "Dequeue"! TThreadList: indefinite growth allowed; TThreadedQueue: STOP! We'll not allow indefinite growth! It's all up to the user now. Also, We'll be pushing and popping, not enqueueing and dequeueing. Also, unlike "ThreadList," we'll call this one "ThreadEDQueue." "That'll keep everyone on their toes!" Happy New Year! Share this post Link to post