When I studied the Android's Asynctask mechanism many years ago, I was fascinated about the implementation of a nice state machine for different stages of an asynchronous task.
C++ multi threading was a bit raw, in the sense that it was not matured in the initial stage. It seems it is catching up.
So let's continue - just for fun...
A thread cannot be preempted while in the critical section. But what about in case the thread is not inside a critical section and the thread function in the background is doing a heavy duty task like downloading a large video files from a remote server which you want to cancel from the UI (the reason for such a system is when the network is slow, the download may take a very long time and hence you want to cancel it midway).
In C++ 20, the stop_token header which has introduced a mechanism by which any thread (obviously not in the critical section) can be interrupted and made exit the thread function. The stop_token header is a nice way of implementing the co-operative thread preemption and cancelling an asynctask midway.
Here's a brief description of it...
std::stop_token:
- Represents the cancellation state of an asynchronous task.
- Doesn't directly control the task, but indicates if a cancellation request has been made.
- Used to check if cancellation is requested within the running task.
std::stop_callback
- An optional callback function registered with a
stop_token
. - Invoked when the associated task is stopped.
Here in the example, the main thread goes to sleep for certain time. In that time frame, the worker thread continues to do the background task.
But after the main thread awakes from the sleeping mode, it requests the worker thread to stop and exit.
Here we go with the source code
The thread function is written as
void printIncrementingValues(std::stop_token stopToken) {
while (!stopToken.stop_requested()) {
std::cout << startNumber++ << " " << std::flush;
std::this_thread::sleep_for(500ms);
}
std::cout << std::endl;
}
Look at the code line in the above code block.
while (!stopToken.stop_requested())
This line is the soul of the whole concept as this keeps listening to the event when the stop_request is issued.
The complete source code is here.
/*
* Worker.h
*
* Created on: Mar 2, 2024
* Author: som
*/
#ifndef WORKER_H_
#define WORKER_H_
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::literals::chrono_literals;
using namespace std;
class Worker {
private:
int startNumber;
public:
Worker(int start){
startNumber = start;
}
virtual ~Worker(){
}
void printIncrementingValues(std::stop_token stopToken) {
while (!stopToken.stop_requested()) {
std::cout << startNumber++ << " " << std::flush;
std::this_thread::sleep_for(500ms);
}
std::cout << std::endl;
}
};
#endif /* WORKER_H_ */
//============================================================================
// Name : advanced_stop_token.cpp
// Author : som
// Version :
// Copyright : som-itsolutions
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <stop_token>
#include <thread>
#include "Worker.h"
int main() {
Worker worker(1);
std::stop_source stopSource;
// creating stop_token object
std::stop_token stopToken = stopSource.get_token();
std::jthread ridit(&Worker::printIncrementingValues, &worker, stopToken);
// Register a callback
std::stop_callback callback(stopToken, []() {
std::cout << std::endl<<"Callback executed" << std::endl;
});
std::this_thread::sleep_for(10s);
stopSource.request_stop();
return 0;
}
And if we run this program, it will show the following
result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Callback executed
As the main thread is in the sleep mode for 10 seconds and the
interval for printing the numbers is set to 500 ms, hence in the
output there will be 1 to 20.
The new C++ is changing very fast - keeping yourself abreast
with the changes is of utmost importance for the engineers who
are using modern C++.