digitalmars.D.learn - DFL background tasks
- Scroph (28/28) Jun 07 2015 Hello,
- Adam D. Ruppe (7/7) Jun 07 2015 For the Invoke call, you should be able to just ignore most the
- Scroph (23/30) Jun 07 2015 Hello Adam, thanks for the fast reply. I tried your code and it
- thedeemon (34/36) Jun 08 2015 Sure, because now you do your long operation in the UI thread.
- thedeemon (28/28) Jun 08 2015 A more general and proper approach is to use message passing from
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (6/9) Jun 08 2015 The owner's thread id is available as 'ownerTid' as well.
- thedeemon (2/5) Jun 08 2015 Oh, thanks! I missed this bit. This is much nicer!
- Scroph (72/72) Jun 10 2015 Briliant, thanks a lot ! Looks like I misunderstood Adam's reply,
- thedeemon (7/9) Jun 10 2015 I don't think this will work as you expect. "perform" is a
Hello, I couldn't find a DFL subforum so I decided to post here, I apologize in advance if this isn't the right place. I'm currently using DFL to write a Youtube downloading program with a simple GUI. I chose DFL because I enjoy the Entice Designer WYSIWYG capabilities, but also because I found DFL to be easy to use after reading the sample codes, especially the Phonebook example. Unfortunately the project is more or less abandoned, and the documentation is incomplete. To make a long story short, there is a ListView that I want to populate with information that could take a while to retrieve (video qualities and links), which temporarily freezes the program. The way I did things thus far was to simply extend the Thread class and pass it the UI elements and let it modify them directly once the information is retrieved, or by just running the task in a delegate that has access to the elements I wish to modify, then passing said delegate to an instance of the Thread class. It worked with TextBoxes and Buttons and other elements, but not with ListView. I did some reading and it turned out this was a frowned upon method, that the UI should only be modified by the UI thread, and that this could be achieved with the "Invoke" did some more digging and found an Invoke method in the Control class, but its signature is hard for me to decipher. I also am not sure how to use this with an onClick event. Control.invoke documentation : http://wiki.dprogramming.com/DflDoc/Control-Control-invoke Any input is appreciated. Thanks in advance.
Jun 07 2015
For the Invoke call, you should be able to just ignore most the arguments and write something like listview.invoke( delegate Object(Object[]) { listview.Add(whatever); return null; }); and it should work.
Jun 07 2015
On Sunday, 7 June 2015 at 12:30:40 UTC, Adam D. Ruppe wrote:For the Invoke call, you should be able to just ignore most the arguments and write something like listview.invoke( delegate Object(Object[]) { listview.Add(whatever); return null; }); and it should work.Hello Adam, thanks for the fast reply. I tried your code and it did work, however it still froze the UI while it was retrieving the data. I'm not sure if I'm using it correctly, I put the invoke call inside a Button.click callback. Here's a stripped down version of the code : https://gist.github.com/Scroph/0f3191fcd8b99db04d4b#file-dfl-d-L14-L17 A few days ago I read abot the XY problem (http://xyproblem.info/), so maybe I'm not asking the right questions. Here's what I'm actually trying to do : after clicking the button, I would like for the UI to remain responsive while : a) A worker thread retrieves the needed information and places it in the ListView or, b) A worker thread (or an asynchronous task maybe ?) retrieves the information and messages the UI with the data when it finishes running. The latter seems to be more idiomatic because it separates the "data retrieval" bit from the "UI modification" bit, but I'm not sure if DFL does things this way. I tried spawn()ing a worker thread from the Button.click callback and receiveOnly(VideoInfo) from it, but this too ended up freezing the UI. Thanks in advance.
Jun 07 2015
On Sunday, 7 June 2015 at 13:34:59 UTC, Scroph wrote:I tried your code and it did work, however it still froze the UI while it was retrieving the data.Sure, because now you do your long operation in the UI thread. What happens in the delegate passed to invoke() happens in the main UI thread. You've got two steps: first some long operation producing some data, then updating the listview with this data. You need to do the long processing part in a worker thread and updating in the UI thread. This is how it's done: in the button callback (which runs in UI thread) you spawn() a thread with some worker function. This worker function does your long operation, and it happens in separate thread so the UI keeps responding. After you finished this long operation you call listview.invoke() with a delegate that just updates the listview, it doesn't do any long operations. This update will happen in the UI thread. Something like: void loadDetailsCallback(Control sender, EventArgs ea) { ... spawn(&loadDetails, qualityBox); // quickly launch a thread // and go on responding to UI events } void loadDetails(ListView qualityBox) { //do the work, this happens in worker thread vid = ... qualityBox.invoke( { // this part happens in UI thread, quickly qualityBox.beginUpdate(); // this belongs out of loop foreach(q; vid.qualities) { qualityBox.addRow(q.itag.to!string, q.type, q.quality, q.link); } qualityBox.endUpdate(); }); }
Jun 08 2015
A more general and proper approach is to use message passing from std.concurrency. With DFL it looks like this: you spawn() a thread and don't pass any GUI controls to it, just thisTid (identifier of your main UI thread). In that worker tread when you've got some data to show in the UI (be it end result of just some status update) you use tid.send(...) and send the data in appropriate messages (defined as separate structs or classes). In the main UI thread you've got a Timer running that checks whether there are any messages in the main thread's mailbox and process them there. Here's an example from a real DFL project: https://bitbucket.org/infognition/undup/src/e8d295b89bc76545860e38a8c9ee171c86f3c84c/newscan.d?at=default#cl-200 OnStart() is a button callback. It does some quick UI updates and spawns a thread, passing relevant data and thisTid: worker = spawn(&makeScan, fname, hdr, thisTid); (makeScan is a function with some long running operation, it's defined in another module) There is also a timer set up when a form is created, and in the timer function OnTimer() there is a check for new messages via receiveTimeout(dur!"msecs"(0). while(receiveTimeout(dur!"msecs"(0), &RcvMsgNumOfDirs, &RcvMsgScanning, &RcvMsgDone)) {} Important part here is to make it non-blocking, it should not sit here waiting for new messages, otherwise UI will not be responsive. And RcvMsgNumOfDirs, RcvMsgScanning, RcvMsgDone are functions that react to corresponding messages sent from the worker thread. They work in the UI thread, of course.
Jun 08 2015
Just a couple of minor improvements: On 06/08/2015 12:22 AM, thedeemon wrote:spawn() a thread and don't pass any GUI controls to it, just thisTid (identifier of your main UI thread)The owner's thread id is available as 'ownerTid' as well.receiveTimeout(dur!"msecs"(0).UFCS makes durations more pleasant: :) receiveTimeout(0.msecs) Ali
Jun 08 2015
On Monday, 8 June 2015 at 07:45:28 UTC, Ali Çehreli wrote:Oh, thanks! I missed this bit. This is much nicer!receiveTimeout(dur!"msecs"(0).UFCS makes durations more pleasant: :) receiveTimeout(0.msecs)
Jun 08 2015
Briliant, thanks a lot ! Looks like I misunderstood Adam's reply, sorry about that ! I tried different things but I didn't think of calling invoke from within the worker thread, that solved the freezing problem. I ended up using the Thread class; spawn complained about the mutability of the given arguments. I realize what I did may be an ugly solution for the problem at hand, so I'll try to modify the code in order to avoid giving the thread UI elements altogether, thus being able to take advantage of more.. modern techniques like std.concurrency. I'm using the Timer trick on another part of the project. Right now I'm running instances of a "Downloader" class in the background, a class that extends Thread and provides some useful functionality : a way to track the progress, to set rate limits, to abort the download, etc. It seems to be working fine, I'm using the (incomplete) program to download Visual Studio as we speak. I set up a timer to periodically retrieve the progress of a download from the instances then update the ListView accordingly. Right now I'm hesitating between continuing with this approach vs using std.concurrency or even std.parallelism since the downloads are supposed to run in parallel without communicating with one another, but the only way I see would be to place the message handling code inside a loop, which for some reason doesn't sit right with me. I'm still learning concurrency (courtesy of Ali Çehreli and his book), so maybe my doubts are unfounded. Here's what I'll probably end up using, minus the error handling : void dlWorker(HTTP client, string local, string remote, bool resume = true) { auto fh = File(local, resume ? "a" : "w"); scope(exit) fh.close; size_t size; size_t progress; client.onProgress = (size_t dlTotal, size_t dlNow, size_t ulTotal, size_t ulNow) { size = dlTotal; progress = dlNow; }; client.onReceive = (ubyte[] data) { fh.rawWrite(data); return data.length; }; client.perform; while(!client.isStopped) { auto msg = receiveOnly!(MsgType, int)(); switch(msg[0]) { case MsgType.progress: send(ownerTid, progress); break; case MsgType.size: send(ownerTid, size); break; case MsgType.abort: client.shutdown; send(ownerTid, 1); break; default: client.handle.set(CurlOption.recv_rate_speed_large, msg[1]); send(ownerTid, 1); break; } } } enum MsgType { progress, size, abort, limit; } Though I'm not entirely sure how I would tie the ListView rows to their respective workers. But I'll cross that bridge when I get to it. Again, thanks for all the help. You guys are definitely going to be mentioned in the "About" section of the finished product.
Jun 10 2015
On Wednesday, 10 June 2015 at 22:18:21 UTC, Scroph wrote:client.perform; while(!client.isStopped)I don't think this will work as you expect. "perform" is a synchronous call, it will not return until the download finishes, as I understand, so your while loop is too late. I think you should insert message processing stuff inside onProgress, though it's also suboptimal (if no progress for long time - no joy). Proper design will require more thought...
Jun 10 2015