0

Introduction

Lets say I have an app with GUI, which gathers some data from the user and then call an embedded python script. I want to add "cancel button" in case the user want to stop the process.

Exemplary code

mainwindow

#include "calc_script.h"

signals:
    void stopWorkSignal();

private:
    calc_script *sender;

private slots:
    Calculating()
    on_pushButton_Cancel_clicked()



void MainWindow::Calculating()
{
QThread* newThread = new QThread();
connect(newThread, &QThread::started,
        [=]() { sender->transfer(val_1, val_2, val_3); });
connect(this,
    SIGNAL(stopWorkSignal()),
    newThread,
    SLOT(deleteLater())
newThread->start();
}

void MainWindow::on_pushButton_Cancel_clicked()
{
    emit stopWorkSignal();
    qDebug() << "stopwork signal emmitted";
}

calc_script.cpp

void calc_script::transfer(double val_1, double val_2, double val_3)
{
///Here the python (from boost.python) is executed
    while(1) {}//this loop will generate a load to mimic this script, you cannot edit it, as the communication with .py is one-side at this lvl
}

The problem When the signal is called I got the error QThread destroyed while thread is still running (and calculation seems to be still going). If I pass SLOT(quit()), nothing happens. If the calculation would be simple loop, I could pass a flag, to brake the loop. But due to calling python script I'm unable to do this, so I'm trying with destroying the Thread which hold the calculations. What's the correct way to do described functionality?

PS. I know I didn't included entire call to python but it is very long. For the reproduction error you can use any non-loop long calculations inside transfer function, it will do basically the same situation.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Karls
  • 731
  • 7
  • 17
  • I described how to reproduce it without python. If you will need any extra data please do not hesitate to ask, I will try to provide it. – Karls Jan 18 '20 at 20:38
  • Saying *non-loop long calculations inside transfer function* is confusing, so it would be better to place that code explicitly, not your original code but any simple code that allows you to reproduce the problem. In addition I am thinking that it might be necessary to modify the .py, if you cannot modify the .py then it would be good to indicate it clearly. – eyllanesc Jan 18 '20 at 20:43
  • I can modify .py, but the script is not communicating in real time with Thread, so probably passing any flag is impossible - at least in my knowledge. If you want to reproduce it just place any endless loop in ```transfer``` (for example ```while(1) {}```) and treat it as this script (you cannot modify it). If it is not enough for you, please notify me, I will try to find other solution for you. I edited the question following your suggestions. – Karls Jan 18 '20 at 21:14
  • With your modification the python and boost is irrelevant since in your question you don't show something that makes different from a `while(true){}` – eyllanesc Jan 18 '20 at 21:20
  • With `while(1) {}` you cannot quit the thread but terminate it. https://stackoverflow.com/questions/12207684/how-do-i-terminate-a-thread-in-c11 Or you need to have `while(!done) {}` where `done` is that condition to organize thread exit. No to mention such tight loop is just a waste of exactly one CPU head. – Alexander V Jan 18 '20 at 21:56
  • This ```while``` is just an substitute, in practise i can't do ```while(!done)```, which is the easiest way - but once again, AFAIK not possible here. I use QThread, not the native C++11, and ```QThread::terminate()``` seems to be somehow ignored or blocked, as it has no effect on the calculation. – Karls Jan 18 '20 at 23:02

1 Answers1

2

You can't forcibly terminate a thread; all you can do is ask it to quit, and then wait for it to exit of its own accord. (there does exist a QThread::terminate() method, but you shouldn't use it in production code, as it will cause problems: for example, if the thread had a mutex locked at the moment it got terminated, that mutex will remain locked forever, and your program will deadlock and freeze up the next time it attempts to lock that mutex).

So you have two options: either figure out a way to ask the Python thread to quit, or use a QProcess object (or something equivalent to it) to run the Python code in a child process instead of inside a thread. The benefit of running the Python code in a separate process is that you can safely kill() a child process -- since the child process doesn't share any state with your GUI process, and the OS will automatically clean up any resources allocated by the child process, there is no problem with the child process leaving mutexes locked or other resources un-freed.

If you'd rather ask the Python thread (or process) politely to exit instead of simply bringing down the hammer on it, you could do so via a networking interface; for example, you could create a TCP connection between your GUI code and the Python event loop, and the Python event loop could periodically do a non-blocking read on its end of the TCP connection. Then when your GUI wants the Python loop to exit, the GUI could close its TCP socket, and that would cause the Python loop's call to read() to return 0 (aka EOF), which the Python loop would know means "time to exit", so it could then exit voluntarily.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Ok, from the proposed solutions, the most elegant way seems to be to quit Python interpreter. But how to do this in boost.python package? The interpreter is initialized by ```Py_Initialize();```. Is there any terminate command? Or maybe I should do this other way? – Karls Jan 19 '20 at 10:06
  • Have your python script call sys.exit(). It needs to be called from the Python thread. – Jeremy Friesner Jan 19 '20 at 14:31
  • ```sys.exit()``` in my case interfere with QTimer, and crash the application, but I can use ```return```. However I don't know how to call it inside python when a button in c++ is pushed. How to execute internal call in python, from external c++ code. – Karls Jan 19 '20 at 14:46
  • My suggestion, as I said in my answer, is to have the c++ program accept a tcp connection from the python thread, and close the tcp connection when you want the python thread to return. The python thread can detect when it’s connection has been closed, and use that detection as a trigger to return immediately. – Jeremy Friesner Jan 19 '20 at 14:49
  • Come to think of it, another option would be to have your Python script periodically call a C or C++ function that you created and exported to Python; this function could return true if it is time to return now, or return false if it is not. See: https://stackoverflow.com/questions/16647186/calling-c-functions-in-python – Jeremy Friesner Jan 19 '20 at 15:03
  • If I'll call C++ function from python, it will return it's value inside C++. This is the way, the boos.python callback works. And it will only have access to python variables. I need more look for a way to update "live" some true/falls variable in python, basing on a variable in C++. Then I could use its state to break the loop with IF statement. But how to do this? As it is different problem, I created another question here: https://stackoverflow.com/questions/59810902/c-and-boost-python-how-to-expose-variable-to-python-and-update-it-in-loop – Karls Jan 19 '20 at 15:31
  • @Kale if you call a C++ function from python, its value gets returned to the Python script. Otherwise there would be no way to return a return-value from the function to the Python script. – Jeremy Friesner Jan 19 '20 at 19:01
  • I was only doing it in opposite way - void callback to C++, to send back some values at every iteration of python loop (Python -> C++). But even if the direction you described is possible, how you would access C++ variable from python scope? Could you edit your answer with code sample, so I can mark it solved if it is correct? – Karls Jan 19 '20 at 19:31
  • I tried with ```int calc::getvalue()``` which has ```return 5``` (just for test). Inside of python: ```test=getvalue()``` ````print(test)```` gives "None". – Karls Jan 19 '20 at 20:09
  • 1
    Dunno how boost does it, but when I want to call a C function from my embedded python script, I need to have my C function return a Python object (e.g. `return PyLong_FromLong(5)`, as shown in the `emb_numargs()` function in the example in section 1.4 of the Embedded Python document at https://docs.python.org/3/extending/embedding.html ) – Jeremy Friesner Jan 19 '20 at 23:10