Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to forward a lambda function to an underlying std::thread constructor in order to execute it in a separate thread?

Given a path, a file watcher is routinely checking its availability (in case of creation/deletion) or timestamp (in case the file exists and its contents have been modified). The code below (take from here) is work in progress and I am adapting it to also support single file (the original code supports multiple where the given path is a directory).

class FileWatcher {
public:
    std::string m_path;
    std::chrono::duration<int, std::milli> m_delay;
    std::unique_ptr<std::thread> m_watcher_thread;

    FileWatcher(std::string path, std::chrono::duration<int, std::milli> delay);

    void start(const std::function<void(std::string, FileStatus)>& action);
private:
    std::unordered_map<std::string, std::filesystem::file_time_type> m_paths;
    bool m_running = true;

    bool contains(const std::string& key);
};

FileWatcher::FileWatcher(std::string path, std::chrono::duration<int, std::milli> delay) : m_path{ path }, m_delay{ delay } {
    // std::this_thread::sleep_for(delay);
    auto const p = std::filesystem::path(m_path);
    if (std::filesystem::is_regular_file(p))
    {
        m_paths[p.string()] = std::filesystem::last_write_time(p);
    }
    else if (std::filesystem::is_directory(p))
    {
        for (auto const& file : std::filesystem::recursive_directory_iterator(m_path)) {
            m_paths[file.path().string()] = std::filesystem::last_write_time(file);
        }
    }
    else
    {
        throw std::exception("Given path to watch is expected to be a regular file or directory");
    }
}

bool FileWatcher::contains(const std::string& key)
{
    auto el = m_paths.find(key);
    return el != m_paths.end();
}

void FileWatcher::start(const std::function<void(std::string, FileStatus)>& action) {
    m_watcher_thread = std::unique_ptr<std::thread>(new std::thread(&action));
    while (m_running) {
       ... // WIP Check status of file and call action
    }
}

I initialize my watcher as follows:

config_watcher = std::shared_ptr<FileWatcher>(new FileWatcher{ "./config.json" , std::chrono::milliseconds(5000) });

and start it by calling (logger_spd is an std::shared_ptr to spdlog logger using a file sink)

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

config_watcher->start([](std::string path, FileStatus status) -> void {
    // Process only regular files, all other file types are ignored
    if (!std::filesystem::is_regular_file(std::filesystem::path(path)) && status != FileStatus::erased) {
        return;
    }
    switch (status) {
    case FileStatus::created:
    {
        std::lock_guard<std::mutex> guard(config_update_mutex);
        logger_spd->info("Configuration created");
        break;
    }
    case FileStatus::modified:
    {
        std::lock_guard<std::mutex> guard(config_update_mutex);
        logger_spd->info("Configuration modified");
        break;
    }
    case FileStatus::erased:
    {
        std::lock_guard<std::mutex> guard(config_update_mutex);
        logger_spd->info("Configuration deleted");
        break;
    }
    default:
        std::lock_guard<std::mutex> guard(config_update_mutex);
        logger_spd->error("Unknown file status");
    }

    std::this_thread::sleep_for(config_watcher->m_delay);
    }
);

The initial code is sub-optimal as mentioned in at least on of the comments below the article I have used as a source because of, among other issues, the std::this_thread::sleep_for() call inside the FileWatcher::start() that blocks the rest of the program unless instantiated in a separate thread. That is why I am trying to modify the code to act more as a typical worker thread, meaning that I would like to pass the action function (argument of FileWatcher::start()) to an underlying thread constructor and then manage everything inside the class.

I am having a problem to wrap my head around the forwarding the lambda to the underlying thread’s constructor through the FileWatcher::start() function.

>Solution :

You should not start the thread with the action, instead you should have a different function that contains the loop and which calls the action inside the loop and use that function for the thread.

Perhaps something like this:

void FileWatcher::start(const std::function<void(std::string, FileStatus)>& action) {
    m_watcher_thread = std::thread([action]() {
        while (m_running) {
            // Wait until something happens...
            action(file, status);
        }
    });
}

Note that I don’t use a pointer for the thread object, as that’s not needed.

The "sleep" should also be inside the loop, and not in the action function.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading