Page 1 of 1

How to use multi-threading techniques to stop a process

Posted: 17 Aug 2015 17:24
by Peter Muraya
I have a process that could go on for a long time and sometimes I need to stop it in a much more elegant way than the Ctl-Alt-Del method that I have been using. I know it has to do with running multiple threads using guards, monitors, etc but I'm not sure how to get started. Here is the single thread version of what I am trying to do

Code: Select all

class facts      terminate:() determ. predicates     onPerform : window::menuItemListener. clauses     onPerform(Source, _MenuTag):-          /*          Clear the terminate flag*/          retractall (terminate()),          /*          Display the stop dialog modelessly*/          stop::display(Source)=_,          /*          Go into an endless loop and abort the process when the Stop button on the stop dialog box is clicked*/          foreach std::repeat(), not (terminate()) do                stdio::write("performing\n")          end foreach.      set_terminate():-assertz(terminate()).
The Stop dialog box implements the following responder

Code: Select all

predicates     onStopClick : button::clickResponder. clauses     onStopClick(_Source) = button::defaultAction:-         taskwindow::set_terminate().
How would you turn this single thread example into multi-thread so that Stop works as intended?

Posted: 18 Aug 2015 8:59
by Thomas Linder Puls
Your usage of repeat and not(terminate()) like that will never terminate. When terminate succeeds not(terminate) will fail, and then you backtrack into repeat again, so you will be looping between repeat and terminate instead of actually terminating.

You can start the background job in a separate thread, but you should notice that a only the GUI (main) thread owns all the windows and is the only thread that is allowed to update them.

So your background thread can for example not write to stdio, likewise you will have to consider how the background thread should update the state of the program with out conflicting with updates performed by the main thread (and other background threads).

Writing to stdio can be solved very simply by posting the write action to the GUI thread so that it performs the actual write.

Your example can look like this:

Code: Select all

clauses     onPerform(...) :-         retractall (terminate()),         thread := thread::start(job).   clauses     job() :-         if not (terminate()) then             postAction({ :- stdio::write("performing\n") }), % We post the writing to the GUI thread             job()         end if.
Using deleyCall you can also let the background task run as events in the GUI thread instead:

Code: Select all

clauses     onPerform(...) :-         retractall (terminate()),         delayCall(1, Job).   clauses     job() :-         if not (terminate()) then             stdio::write("performing\n"), % We are in the GUI thread             delayCall(1, Job)         end if.
You will need a delay, otherwise the background task will starve the normal event handling.

Running in the GUI thread like that is much simpler when it comes to accessing and updating the data in the program.

Posted: 18 Aug 2015 17:23
by Peter Muraya
Hi Thomas.
Yes, you are right, the piece as I had written would never terminate.

I have used the delayCall method to build and test the solution. It works very well. Here it is, complete with a counter to show me that the job is running.

Code: Select all

class facts      terminate:() determ. predicates     onPerform : window::menuItemListener. clauses     onPerform(Source, _MenuTag):-          /*          Clear the terminate flag*/          retractall (terminate()),          /*          Display the stop dialog modelessly*/          stop::display(Source)=_,            /*          Run the long job*/          delayCall(1, job).    /*  The looooong job*/  facts      counter:varM{integer}:=varM::new(0).  predicates      job:().  clauses      job():-           stdio::write("performing ", counter:value, "\n"),           if                terminate()           then                exception::raise_error("aborted")           else                counter:value := counter:value+1,                delayCall(1, job)           end if.      set_terminate():-assertz(terminate()).  

Under what (special) conditions would you use the thread::start approach with all the conditions it imposes on access to the resources of the main thread?

Posted: 18 Aug 2015 18:48
by Thomas Linder Puls
With the delayCall approach each cycle in the job will run in the GUI thread. Therefore it will have to be short otherwise the GUI will become unresponsive.

The real multithreaded approach can run as long as you like without affecting the GUI thread.

Posted: 19 Aug 2015 6:51
by Peter Muraya
Thanks, Thomas.
Let me get this right as I think it is important for choosing the best approach. Do you mean that the Do_the_real_work() predicate in the code below would have to be short/fast in the delayCall method to avoid making the main thread unresponsive? And that this constraint does not apply to the thread::start method?

Code: Select all

clauses     /*     Using thread::start method*/     job() :-         if not (terminate()) then             Do_the_real_work(),             job()         end if.         /*     Using the delayCall method*/       job() :-         if not (terminate()) then             Do_the_real_work(),             delayCall(1, Job)         end if.

Posted: 19 Aug 2015 8:45
by Thomas Linder Puls

Posted: 19 Aug 2015 19:16
by Peter Muraya
Thanks Thomas.
I was motivated to look at the multithread package by your thread::start method; there is quite a lot of stuff there! I will have a look at the Language reference to see if I can make sense of it. Thanks.