Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QFuture destruction before thread completion



  • I am sending a blocking operation that is started by user input to another thread, but if the user provides further input while the thread is running, the output of that thread is no longer needed. So I would like to terminate the thread, but since that isn't a realistic option, it seems like I should let the thread complete but disregard the result. If I create a QFuture using Concurrent::run, and the future is destroyed before the thread completes, does it cause a memory leak or some other problem? If so, is there another method I could use to sweep my unneeded threads under the rug?

    Ideally, I would design the blocking operation to be cancellable, but the operation is a single library function call. I can modify the library to allow that behavior, but it would be a lot of work and isn't really in the scope of the library.

    Thanks.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    How are you destroying that QFuture ?

    What is your current architecture ?



  • I have tried a few ways to test how it behaves, at the moment I have a std::shared_ptr<QFuture<QImage>> that I am resetting with a new QFuture before starting the new computation. I also tried just holding QFuture<QImage> in my class and setting it to the output of run when I need to. No idea if either way behaves the way I intend it to though. I am also using a QFutureWatcher to signal an update when the thread finishes and I am not sure exactly how that interacts with the watched QFuture being destroyed. Testing both of those ideas led to crashes. I can't be sure the crashes aren't being caused by some other error, but the crashes are a read access violation in QFuture methods.

    I'm not sure what you mean by architecture.


  • Lifetime Qt Champion

    By architecture, I mean your software architecture.

    In the absolute, the best solution would be to refactor that library to make it cancellable as your are wasting resources.

    As for your QFuture, what about just using cancel and its friends ?



  • I found the error that was causing my crashes, everything seems to be working now. I created a connection in the "lazy" way of connect( object1, signal, [object2](){ do stuff to object2 } ); when I was testing how the class should work. The connection wasn't being disconnected when object2 was destroyed and it was causing seemingly random crashes later on.

    My original question, however, still stands. Do I need to destroy and recreate my futures, or can I safely assign a new future from run to a future who's thread is still running? Does the situation change when a watcher is observing that future?

    I am considering modifying the library, but it isn't Qt-based, so I'll have to use other tools to make that happen.

    I was under the impression that cancel did nothing for futures created with run.



  • @Logan-McBroom said in QFuture destruction before thread completion:

    I was under the impression that cancel did nothing for futures created with run.

    A quick look at the documentation suggests that cancel does exactly what you want:

    Cancels the asynchronous computation represented by this future. Note that the cancelation is asynchronous. Use waitForFinished() after calling cancel() when you need synchronous cancelation.

    What I read here is that the future only says it will not use the result of any computation anymore. As you suggested, it will not abort the running thread (that's why it suggests calling waitForFinished() if you need synchronous cancelation – which you don't).

    IIRC QFuture normally waits in its destructor. cancel would then help to not wait (even though it does not stop the running thread).



  • Thanks for the reply Simon. The reason I did not use cancel is the line at the bottom of that documentation that says: "Be aware that not all running asynchronous computations can be canceled. For example, the future returned by QtConcurrent::run() cannot be canceled." Are you sure cancel has any effect in my case?

    How do you make the nice looking quotes?


  • Qt Champions 2019

    @Logan-McBroom said in QFuture destruction before thread completion:

    How do you make the nice looking quotes?

    By pressing "Quote" instead of "Reply"



  • @Logan-McBroom said in QFuture destruction before thread completion:

    Thanks for the reply Simon. The reason I did not use cancel is the line at the bottom of that documentation that says: "Be aware that not all running asynchronous computations can be canceled. For example, the future returned by QtConcurrent::run() cannot be canceled." Are you sure cancel has any effect in my case?

    I see. I haven't worked with QtConcurrent::run() in a long while and I am not sure about the exact behavior. So, you should write a small test program to figure out how canceled futures work with QtConcurrent::run() in their destructors (waiting or not). The addition in the documentation might try to indicate that cancel will not terminate the thread immediately. It goes on by saying that "but the future returned by QtConcurrent::mappedReduced() can". mappedReduced calls the same function on all elements of a container. Hence, cancel can intervene between different 'iterations' of the mapping call. However, I still expect that for long operations on each element in the container it will also not immediately terminate the thread.

    I am personally trying out different variants of parallel execution. A lot of times I have sequential code that I need to transform. Quickest is a simple

    QThread::create([this,...]() { /* do work */; QThread::currentThread()->deleteLater(); })->start();
    

    Sometimes I have some code that needs to be executed in the old thread (mostly because it is GUI-related stuff) after the thread is done:

    auto thread = QThread::create([this,...]() { /* do work */ });
    connect(thread, &QThread::finished, qApp, [this,...]() { /* finish up work */ });
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    thread->start();
    

    In this case I don't have to rewrite the code, but only put most of the code into a lambda function. Crucial is that this does not happen all the time in order to not have to many threads running in parallel. (QtConcurrent::run() launches tasks in a thread pool.)

    Another way is to have a worker thread where you queue commands:

    QThread *thread = new QThread();
    thread->start();
    
    // wait until the event queue is actually running
    while(!QAbstractEventDispatcher::instance(thread))
        QThread::currentThread->msleep(1);
    
    QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(thread),
        [this,...]() { /* do work */ });
    

    Now, you can queue multiple work packages that run one after another. Doing things at the end or 'returning' a result would be done by queuing something back to the other thread (which is the main thread in my case):

    // from within the thread
    QMetaObject::invokeMethod(qApp, [this,...]() { this->setMember(someValue); ... });
    

    The nice thing is that this is not blocking.

    How do you make the nice looking quotes?

    Just start a line with >



  • For anyone reading this in the future: as far as I can tell there is no problem with assigning a fresh QFuture to one that is already running. It seems to behave as expected, letting the disconnected thread complete in the background and eventually cleaning it up. Do take this with a grain of salt as if I were 100% about it I wouldn't be asking on a forum.


Log in to reply