Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QProcessDialog with multiple QFutureWatchers

QProcessDialog with multiple QFutureWatchers

Scheduled Pinned Locked Moved General and Desktop
4 Posts 2 Posters 1.9k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • TranquilT Offline
    TranquilT Offline
    Tranquil
    wrote on last edited by
    #1

    Hello everyone,

    I would need assistance for multi-threading a function. Imagine you had a class Data, and its object data. You have names of files in vector of strings data.names, and their content in a map data.content. Now, there is a function doProcessThread, that should be run in each spawned thread, while proper arguments are passed (int, Data, vector). The function modifies the content of the data (each thread modifies one element of the map). I wrote the code and it does the job just fine. However, reporting back to the QProcessDialog is not correct. As you see in the code below, I am generating multiple QFutureWatcher objects (pointers actually), and connecting all of them with a dialog. As you may guess, the dialog closes with the first finished thread, even though others are still running. If I place it after the loop for waitForFinished, it will never go away on its own. Also, if I press cancel on the dialog, it ignores. It is not showing any percentage or incrementing. Could you give me some pointers what would be the best way to address this? Thank you.

    void Processer::doProcessMulti(Data& data){
    
        QProgressDialog dialog;
        dialog.setLabelText(QString("Processing files..."));
    
        vector<QFutureWatcher<void> *> watchers;
    
        for (unsigned int i = 0; i < data.names.size(); i++) {
    
                QFutureWatcher<void>* futureWatcher = new QFutureWatcher<void>();
    
                QObject::connect(&dialog, SIGNAL(canceled()), futureWatcher, SLOT(cancel()));
                QObject::connect(futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
                QObject::connect(futureWatcher, SIGNAL(progressRangeChanged(int,int)), &dialog, SLOT(setRange(int,int)));
                QObject::connect(futureWatcher, SIGNAL(progressValueChanged(int)), &dialog, SLOT(setValue(int)));
                
                futureWatcher->setFuture(QtConcurrent::run(this, &Processer::doProcessThread, i, std::ref(data), someVector));
                watchers.push_back(futureWatcher);
        }
    
        dialog.exec();
    
        for (unsigned int i = 0; i < watchers.size(); i++) {
            watchers[i]->waitForFinished();
        }
    }
    
    1 Reply Last reply
    0
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by Chris Kawa
      #2

      This type of task is better handled by QtConcurrent::map than multiple calls to QtConcurrent::run. With map you get a single future with progress indication. With run you'd need to handle that manually but why re-invent the wheel?
      To use map, if possible, convert your Data structure to more processing friendly form i.e. a single container of stuff instead of stuff of two containers.
      That would be great but if that's not possible for some reason you can create a lightweight "adapter" struct like this:

      struct Params {
          const QString* fileName; //pointing to an element in the data.names container
          WhateverContent* content; //pointing to an element in the data.content container
          Output* output; //pointing to an element in someVector
      };
      

      and then adapt your Processer::doProcessThread to take that as a single parameter.

      Other comments:

      • Consider switching to the new connect syntax (a lot less error prone).
      • Mind your object ownership. You are leaking all the QFutureWatcher instances. Either give them parents or delete them manually. Better yet use map and create only one on the stack.
      • Don't reset when you mean close. Result is the same in this case but it's better to explicitly state the intent.

      So a skeleton code for this (may have bugs, flows out of my head ;) ):

      void Processer::doProcessMulti(Data& data){
          using Fut = QFutureWatcher<void>; //just because it's less typing later
          using Pd  = QProgressDialog;
      
          Pd dialog; //you should probably give it a parent to have proper windowing order, parents are not only for resource mangement
          dialog.setLabelText("Processing files..."); //let automatic conversion kick in, you don't need to spell out QString
      
          Fut watcher; //just make one on the stack so you don't leak it
      
          //If you're in a QObject you don't need to spell out QObject::connect every time
          connect(&dialog,  &Pd::canceled,              &watcher, &Fut::cancel);
          connect(&watcher, &Fut::finished,             &dialog,  &Pd::close);
          connect(&watcher, &Fut::progressRangeChanged, &dialog,  &Pd::setRange);
          connect(&watcher, &Fut::progressValueChanged, &dialog,  &Pd::setValue);
      
          QVector<Params> stuff = adaptFromData(data); //make adaptFromData create the "helper" structs
          watcher.setFuture(QtConcurrent::map(stuff, &Processer::doProcessThread)); //assuming doProcessThread takes &Params
      
          dialog.exec();
      }
      
      1 Reply Last reply
      2
      • TranquilT Offline
        TranquilT Offline
        Tranquil
        wrote on last edited by
        #3

        Hi Chris,

        Thanks a lot for your reply, I really appreciate all your comments and help!
        My Data class cannot really be converted into a container of any sort, that is why I kept using QtConcurrent::run (I could feed it with the whole object). Can QtConcurrent::map use QMap for iteration?

        I followed your suggestions carefully and created an "adapter" struct, and re-defined doProcessThread function (so that it takes a reference to that struct). I used QVector to store instances of that struct, placed the vector as a first parameter in ::map, but I got an error of not matching for call to ‘(QtConcurrent::MemberFunctionWrapper1<void... I looked it up, and tried with lambda expression, but got another error: undefined reference to `QtConcurrent::ThreadEngineBase::~ThreadEngineBase().

        In the end, I decided not to complicate it further, but to think of a better design for my containers and the functions that modify them. I will get back to this post once I try it again. I will need to re-organize all my functions and variables so that I can use multi-threading in such a way.

        1 Reply Last reply
        0
        • Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #4

          The doProcessThread signature must look something like this to work (map will not work with non-static members):

          class Processer{
          public:
             static void doProcessThread(Params& params);
          };
          

          If it can't be static and you need that instance objects then a lambda adapter like this should work:

          //assuming "this" is the Processer instance you want to use, otherwise adjust
          watcher.setFuture(QtConcurrent::map(stuff,  [this](Params& params){ doProcessThread(params); }));
          

          Can QtConcurrent::map use QMap for iteration?
          It can use any iterator based container. The gotcha with QMap is that, unlike in std::map, its iterator's operator* returns a reference to the value, so there's no way to get to the key this way. So if you used a map like QMap<String, Stuff> you would get Stuff& parameter in the map call with no way to get to the string.
          In contrast, the std::map's iterator is a pair of key/value, so with std::map<QString, Stuff> you would get a std::pair<QString, Stuff>& parameter in the map call.

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved