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. Continuation not working with QtConcurrent::mapped
Forum Updated to NodeBB v4.3 + New Features

Continuation not working with QtConcurrent::mapped

Scheduled Pinned Locked Moved Unsolved General and Desktop
20 Posts 6 Posters 797 Views 4 Watching
  • 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.
  • A Asperamanca

    @J-Hilk No, I just use a ranged-for loop. Since QFuture models a linear container, that should be equivalent.

    J.HilkJ Offline
    J.HilkJ Offline
    J.Hilk
    Moderators
    wrote on last edited by
    #10

    @Asperamanca it should,

    I don't usually use QtConcurrent and nearly never .then, So I'm also kinda driving blind here :D


    Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


    Q: What's that?
    A: It's blue light.
    Q: What does it do?
    A: It turns blue.

    1 Reply Last reply
    0
    • J.HilkJ J.Hilk

      @JonB do you use a QFutureIterator to iterate over the returned result ?

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #11

      @J-Hilk said in Continuation not working with QtConcurrent::mapped:

      @JonB do you use a QFutureIterator to iterate over the returned result ?

      Nope. I work like @Asperamanca. And confirm resultCount() == 1.

      I don't usually use QtConcurrent and nearly never .then, So I'm also kinda driving blind here :D

      :D

      @Asperamanca said in Continuation not working with QtConcurrent::mapped:

      EDIT: I think I'll prepare a full example and post a bug report on this.

      Please post link here and I will join, I too would like to understand whether this behaviour is intended or accidental.

      1 Reply Last reply
      0
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #12

        Hi,

        The documentation of then mentions:

        The continuation can also take a QFuture argument (instead of its value), 
        representing the previous future. This can be useful if, for example, QFuture has 
        multiple results, and the user wants to access them inside the continuation. Or the 
        user needs to handle the exception of the previous future inside the continuation, 
        to not interrupt the chain of multiple continuations.
        

        Wouldn't that match your use case ?

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        JonBJ A 2 Replies Last reply
        0
        • SGaistS SGaist

          Hi,

          The documentation of then mentions:

          The continuation can also take a QFuture argument (instead of its value), 
          representing the previous future. This can be useful if, for example, QFuture has 
          multiple results, and the user wants to access them inside the continuation. Or the 
          user needs to handle the exception of the previous future inside the continuation, 
          to not interrupt the chain of multiple continuations.
          

          Wouldn't that match your use case ?

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #13

          @SGaist
          I read all the stuff like this, and it did not enlighten me. If you think the OP's requirement --- calling a function in then() either on an accumulated QList<double> or on individual doubles and whatever wrapping in the way of QFuture around it --- please show an extract of code you propose which (a) compiles! and (b) processes all doubles returned. Using mapped() and then(). Because I cannot.

          1 Reply Last reply
          0
          • SGaistS SGaist

            Hi,

            The documentation of then mentions:

            The continuation can also take a QFuture argument (instead of its value), 
            representing the previous future. This can be useful if, for example, QFuture has 
            multiple results, and the user wants to access them inside the continuation. Or the 
            user needs to handle the exception of the previous future inside the continuation, 
            to not interrupt the chain of multiple continuations.
            

            Wouldn't that match your use case ?

            A Offline
            A Offline
            Asperamanca
            wrote on last edited by
            #14

            @SGaist I don't see how that is very useful with longer pipelines and multiple results in practice. It puts the whole burden of handling multiple, parallel executions on the continuation function (=me). I could just as well use QtConcurrent::run with a QList<double> as single input, and write all my functions to handle a container instead of a single value.
            All the advantages of optimizing the thread usage would be gone, because at every stage, you'd have to wait for all results to be in (even if I used a nested QtConcurrent::mapped inside my continuation)

            Or you'd start writing something that can handle QFuture<QFuture<QFuture<double>>>, but where would that end?

            1 Reply Last reply
            0
            • A Offline
              A Offline
              Asperamanca
              wrote on last edited by
              #15

              BTW, here's a kind of workaround when you want to run a continuation on a range of things:

              auto fMakeFuture = [](const double input)
                  {
                      return QtConcurrent::run(calculateArea, input)
                                         .then(formatArea);
                  };
              
              // We have to create the futures eagerly, otherwise the threads won't start
              // views are lazy, use 'normal' ranges::transform instead
              std::vector<QFuture<std::string>> vecFutures;
              std::ranges::transform(vecInputs, std::back_inserter(vecFutures), fMakeFuture);
              
              
              // Access the results lazily. We'll only block when we actually start using a result
              auto viewResults = vecFutures
                                 | std::views::transform([](const auto& future) { return future.result(); });
              
              1 Reply Last reply
              1
              • A Asperamanca

                I'm testing QtConcurrent with continuation ('then'). The following sequence does what I expect: It gives me a future with 5 results:

                    QList<double> inputs{2.0, 3.0, 5.0, 10.0, 12.0};
                    auto future = QtConcurrent::mapped(inputs, calculateArea);
                    future.waitForFinished();
                

                However, when I use a continuation to add a second stage to the pipeline, I only get the first result:

                    QList<double> inputs{2.0, 3.0, 5.0, 10.0, 12.0};
                    auto future = QtConcurrent::mapped(inputs, calculateArea)
                                                      .then(formatArea);
                    future.waitForFinished();
                

                Any ideas? I would have expected either a compiler error, a runtime error, or the correct number of results.

                JKSHJ Offline
                JKSHJ Offline
                JKSH
                Moderators
                wrote on last edited by JKSH
                #16

                @Asperamanca said in Continuation not working with QtConcurrent::mapped:

                    QList<double> inputs{2.0, 3.0, 5.0, 10.0, 12.0};
                    auto future = QtConcurrent::mapped(inputs, calculateArea)
                                                      .then(formatArea);
                    future.waitForFinished();
                

                The API is not very nice... but the following should work (although it's less parallelized than your fMakeFuture approach):

                auto future = QtConcurrent::mapped(inputs, calculateArea)
                                           .then([](QFuture<double> intermediate) { return QtConcurrent::mapped(intermediate.results(), formatArea); })
                                           .unwrap();
                qDebug() << future.results(); // Automatically waits for finished
                

                Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                1 Reply Last reply
                0
                • JKSHJ Offline
                  JKSHJ Offline
                  JKSH
                  Moderators
                  wrote on last edited by JKSH
                  #17

                  @Asperamanca said in Continuation not working with QtConcurrent::mapped:

                  BTW, here's a kind of workaround when you want to run a continuation on a range of things:

                  I wrote my Rube-Goldberg solution above, then admired your solution, then realized:

                  auto future = QtConcurrent::mapped(inputs, [](double input) {
                      return formatArea(calculateArea(input));
                  });
                  

                  Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                  1 Reply Last reply
                  2
                  • S Offline
                    S Offline
                    SimonSchroeder
                    wrote on last edited by
                    #18

                    I guess that the intention is to have something like 2 mapped calls chained, so that both operations are executed in parallel on the elements. You also expect that when one element is finished with the first mapping it is immediately available for the second mapping.

                    What we have figured out so far is that mapped() returns a future. The second mapped (if it somehow would work) would wait for the full future to complete.

                    Also remember that thread communication/synchronization has heavy overhead. If you chain two different mapped calls together you will have this overhead twice. This means it is quite sensible to put the two function calls into a single mapped call. I can't think of anything in the STL to merge two function calls to be passed as a new function pointer. You could come up with something like this (untested!):

                    template<class T, class U, class V>
                    auto chain(U(T) first, V(U) second) -> T(V)  
                    {
                        return [=](T &arg){ return second(first(arg)); };
                    }
                    

                    It can be called like this:

                    auto future = QtConcurrent::mapped(inputs, chain(calculateArea, formatArea));
                    

                    (Only while writing this I noticed that it is really close to the other solutions using a lambda.)

                    BTW, performance will be really bad if 1) the amount of work to do for each mapping is just tiny or 2) elements of the array are tiny and the threads will battle caching on different cores. Both can be addressed by handling the mapping in chunks. A much easier way to do this (that I know of) is OpenMP:

                    const int size = inputs.size();
                    #pragma omp parallel for shared(inputs,ouputs)
                    for(int i = 0; i < size; ++i)
                    {
                        double tmp = calculateArea(inputs[i]);
                        outputs[i] = formatArea(tmp);
                    }
                    

                    The #pragma omp handles everything. You can specify if you want to synchronize after the loop, you can pick from different schedules, and set the chunk size of the schedule. Also, the actual source code is still quite readable (it is just an annotation of regular C++ code with a few restrictions on the loop).

                    A 1 Reply Last reply
                    0
                    • S SimonSchroeder

                      I guess that the intention is to have something like 2 mapped calls chained, so that both operations are executed in parallel on the elements. You also expect that when one element is finished with the first mapping it is immediately available for the second mapping.

                      What we have figured out so far is that mapped() returns a future. The second mapped (if it somehow would work) would wait for the full future to complete.

                      Also remember that thread communication/synchronization has heavy overhead. If you chain two different mapped calls together you will have this overhead twice. This means it is quite sensible to put the two function calls into a single mapped call. I can't think of anything in the STL to merge two function calls to be passed as a new function pointer. You could come up with something like this (untested!):

                      template<class T, class U, class V>
                      auto chain(U(T) first, V(U) second) -> T(V)  
                      {
                          return [=](T &arg){ return second(first(arg)); };
                      }
                      

                      It can be called like this:

                      auto future = QtConcurrent::mapped(inputs, chain(calculateArea, formatArea));
                      

                      (Only while writing this I noticed that it is really close to the other solutions using a lambda.)

                      BTW, performance will be really bad if 1) the amount of work to do for each mapping is just tiny or 2) elements of the array are tiny and the threads will battle caching on different cores. Both can be addressed by handling the mapping in chunks. A much easier way to do this (that I know of) is OpenMP:

                      const int size = inputs.size();
                      #pragma omp parallel for shared(inputs,ouputs)
                      for(int i = 0; i < size; ++i)
                      {
                          double tmp = calculateArea(inputs[i]);
                          outputs[i] = formatArea(tmp);
                      }
                      

                      The #pragma omp handles everything. You can specify if you want to synchronize after the loop, you can pick from different schedules, and set the chunk size of the schedule. Also, the actual source code is still quite readable (it is just an annotation of regular C++ code with a few restrictions on the loop).

                      A Offline
                      A Offline
                      Asperamanca
                      wrote on last edited by
                      #19

                      @SimonSchroeder said in Continuation not working with QtConcurrent::mapped:

                      What we have figured out so far is that mapped() returns a future. The second mapped (if it somehow would work) would wait for the full future to complete.

                      Why? That would be up to the implementation. It could track individual results (if there are many), and start those that can start.

                      Also remember that thread communication/synchronization has heavy overhead. If you chain two different mapped calls together you will have this overhead twice. This means it is quite sensible to put the two function calls into a single mapped call. I can't think of anything in the STL to merge two function calls to be passed as a new function pointer. You could come up with something like this (untested!):

                      Overhead, sure. It's my responsibility to only start work this way where it makes sense to move it to separate threads. My example is certainly not a good candiate :-)

                      In general, I wouldn't mind if ".then" on a "mapped" future simply wouldn't compile (best), or the behavior was documented and I got a runtime error (also ok). That is silently drops everything but the first result is "unexpected behavior".

                      I wonder about the design decision to make a QFuture both a single value AND a container...but I guess, that ship sailed long ago.

                      1 Reply Last reply
                      1
                      • A Offline
                        A Offline
                        Asperamanca
                        wrote on last edited by
                        #20

                        Bug posted as https://bugreports.qt.io/browse/QTBUG-133522

                        1 Reply Last reply
                        3

                        • Login

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