Cancel button for scripts with long runtime

Hi all,

There are many scripts, such as the publicly available Kern Crasher that have a long runtime, particularly if they are run on a large character set. Once the script is started, it is not possible to stop it as the UI (which uses vanilla) freezes. The only option is to either wait until it has finished or to kill the Glyphs process.

I’ve been trying to implement a “Cancel” button for such a script, but haven’t been able to get it to work. Based on the examples here I have tried running the processor intensive code in a new thread, but the GUI still freezes when processing and the Cancel button is only polled at the end.

I have also tried opening the GUI window in its own thread, but this caused Glyphs to crash and I have been since informed that GUI code should always be in the main thread.

Can anyone with more experience in multithreading and vanilla help me here? Perhaps this problem has been solved already?

Thanks in advance!

2 Likes

This should work.

  • make sure that the main script finishes when it started the task on another thread.
  • in that thread, poll the Cancel button state or better a property that is set when the Cancel button is pressed and stop the task if needed.

Thanks, Georg, for your reply. I think I know why it wasn’t working before: I wanted the main script to wait for the task in the thread to finish (or be cancelled), as there are further processing steps to be carried out afterwards, and I used thread.join() to do this. But this caused the GUI to lock up again.

Is there a way to do this (it would make the code easier to read and maintain) or do I need to make the thread aware of where it is in the processing pipeline and start the next task from there?

Thanks!

The script runs in the main thread. And as long as the script is not returning the control over to the app, the UI is blocked. I’m not familiar with multi threading in python so I can can’t help with this.

Thanks @GeorgSeifert for your help on this. Modifying the code according to your suggestion:

  • make sure that the main script finishes when it started the task on another thread.

has made it possible to click the Cancel button and abort processing. But now the progress bar, which belongs to the main thread, does not get updated during processing, and presumably it shouldn’t be updated from within the thread, anyway.

Other GUI toolkits such as tkinter have listeners for such cases. Is there a way to implement this in vanilla?

You need to add a method that updates the progress bar. From your background thread, you need to call this method but from the main thread. There needs to be a way to say: “run this on the main thread”. Not sure how that is done in python.

In ObjectiveC, you can do:

selector = objc.selector(object.updateProgress_, signature=b"v@:@")
object.performSelectorOnMainThread_withObject_waitUntilDone_(selector, percent, False)

object needs to be a NSObject, so if you are in a plugin, that plugin could be used.

1 Like