c++opencvconcurrencyfuturestdasync

Using std::async with a method receiving a cv::OutputArray in order to assign it doesn't work


I have the following function:

void MyClass::myFunc(cv::OutputArray dst) const {
    cv::Mat result;
    ...
    dst.assign(result);
}

If I run it like this:

cv::Mat result;
myFunc(result);
otherFunc(result);

It works fine and otherFunc recieves result modified by myFunc.

But if I use std::async like this:

cv::Mat result;
auto resultFuture = std::async(&MyClass::myFunc, this, result);
resultFuture.wait();
otherFunc(result);

otherFunc receives empty result. What am I doing wrong?


Solution

  • The root cause is that passing arguments by reference (&) to a function to run via std::async is problematic. You can read about it here: Passing arguments to std::async by reference fails (in your case there is no a compilation error, but the link explains the issue in general).

    And in your case you use cv::OutputArray which is defined in opencv as a refernce type:

    typedef const _OutputArray& OutputArray;
    

    I assume you wanted the reference semantics, since you expected your result object to be updated by myFunc.

    The solution is to use std::ref.
    But since the result object you pass is a cv::Mat, it preferable and more straightforward that myFunc will receive a cv::Mat&.
    I also managed to produce a solution using a cv::OutputArray, but it requires an ugly cast (in addition to the std::ref). It works fine on MSVC, but I am not sure it is will be generally valid.

    Below is the code demostrating these 2 options. I recomend to use the 1st approach if you can. You can call otherFunc(result); at the point where I print the dimensions of result after it is initialized.

    #include <opencv2/core/core.hpp>
    #include <future>
    #include <iostream>
    
    // Test using a cv::Mat &:
    class MyClass1
    {
        void myFunc(cv::Mat & dst) const
        {
            cv::Mat result(4, 3, CV_8UC1);
            result = 1; // ... initialize some values
            dst = result;   // instead of using cv::OutputArray::assign
        }
    public:
        void test()
        {
            cv::Mat result;
            std::cout << "MyClass1: before: " << result.cols << " x " << result.rows << std::endl;
            auto resultFuture = std::async(&MyClass1::myFunc, this, std::ref(result));
            resultFuture.wait();
            // Here result will be properly set.
            std::cout << "MyClass1: after: " << result.cols << " x " << result.rows << std::endl;
        }
    };
    
    // Test using a cv::OutputArray:
    class MyClass2
    {
        void myFunc(cv::OutputArray dst) const
        {
            cv::Mat result(4, 3, CV_8UC1); 
            result = 1; // ... initialize some values
            dst.assign(result);
        }
    public:
        void test()
        {
            cv::Mat result;
            std::cout << "MyClass2: before: " << result.cols << " x " << result.rows << std::endl;
            auto resultFuture = std::async(&MyClass2::myFunc, this, std::ref(static_cast<cv::OutputArray>(result)));
            resultFuture.wait();
            // Here result will be properly set.
            std::cout << "MyClass2: after: " << result.cols << " x " << result.rows << std::endl;
        }
    };
    
    int main()
    {
        // Test receiving a cv::Mat&:
        MyClass1 m1;
        m1.test();
        // Test receiving a cv::OutputArray:
        MyClass2 m2;
        m2.test();
        return 0;
    }