2

I don't think the title describes the situation exactly. I apologize for the poor description.

I'm trying to write a software system, in C++, that will have a heavy job (scanning the file system for a particular file or for infections, or just copying/moving a file from one place to another). This system has an interactive component which provides an opportunity for the user to pause and resume the operation. Given that we have a user interface, to achieve separation of concerns, I'm planning to have the job done on a separate thread of execution (within the same process) or a different process (if the interface and operation should be separated by process boundaries). I do not want the UI request handling logic creep into the job handler at all. Now, how should I design the system ? What (synchronization) primitives should I use here ?

I'm new to asynchronous programming. I understand that, the data structures to maintain (progress, current item in progress, checking integrity while resuming, part of job that is completed yet) will purely depend on the application. However, is there a general way to approach this problem ? I'm looking for a platform independent approach.

I assuming this is arguable and there may not be one correct answer, but any suggestions would help.

  • 1
    Normally you'd design the scanning component (the worker) as a state machine who's interface would provide the ability to start/stop/pause/subscribe to progress events etc. And then write the UI to talk to that interface. – Richard Hodges Jul 3 '18 at 11:19
  • @RichardHodges. Thanks for your suggestion. I'm not sure how to implement this concretely. Do you suggest the worker pick jobs from a queue and the user interface handler to have rights to preempt the queue ? or queue a special type of job that the worker recognizes to pause/resume ? If yes, what about jobs that don't have this inherent property being split into smaller ones ? – rranjik Jul 3 '18 at 11:31
  • 1
    @user3222: Typically, the pause/resume mechanism is simply a flag that the worker thread periodically checks. This is independent of the queue used to hand the jobs to the worker thread. – Bart van Ingen Schenau Jul 3 '18 at 11:38
  • @user3222 whether the worker state machine should queue requests or act immediately is a design choice. If the jobs must run to completion or die then there are a few mechanisms. You can either poll an "abort" flag during the job periodically or you can use interruption points (see boost thread's interruption_point mechanism) for controlled thread interruption. Then of course there is the asio-style executor loop for controlling job continuation (I personally favour this approach). – Richard Hodges Jul 3 '18 at 12:40
2

I've seen this done with a job processing class that runs on its own thread. It looked something like this:

class JobQueue {
public:
    JobQueue();
    ~JobQueue();

    void EnqueueJob(Job newJob);
    void ResumeProcessing();
    void PauseProcessing();
    bool IsProcessing();

private:
    std::queue<Job> jobs;
    bool processing {false}; // May need more states, but keeping it simple here
    mutex jobQueueMutex;

    void ProcessLoop();
};

The mutex can be another type of primitive if that's more convenient/efficient for your needs. But a typical implementation would be something like this:

void JobQueue::EnqueueJob(Job newJob)
{
    std::lock_guard<std::mutex> lk(jobQueueMutex);

    jobs.push_back(newJob);
}

void JobQueue::ResumeProcessing()
{
    std::lock_guard<std::mutex> lk(jobQueueMutex);

    processing = true;
}

void JobQueue::PauseProcessing()
{
    std::lock_guard<std::mutex> lk(jobQueueMutex);

    processing = false;
}

bool IsProcessing()
{
    std::lock_guard<std::mutex> lk(jobQueueMutex);

    return isProcessing;
}

void JobQueue::ProcessLoop()
{
    while (1)
    {
        std::unique_lock<std::mutex> lk(jobQueueMutex);
        Job* nextJob = nullptr;
        if (jobs.size() > 0)
        {
            nextJob = jobs.pop_front();
        }
        lk.unlock();

        if (nextJob != nullptr)
        {
            nextJob->Process();
        }

        while (!isProcessing) {} // Probably don't want to busy wait, just an example. Could use semaphore or other mechanism
    }
}

The idea here is that you would call EnqueueJob() on the UI thread or any other thread. You can pause, resume, or check whether processing is occurring on any thread as well. The ProcessLoop() function would run on the processing thread.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.