Move thread class to base lib (the new thread class has a C++0x-like api).

This commit is contained in:
David Capello 2010-10-02 19:00:01 -03:00
parent 2a73d6ffb9
commit b66371f95a
16 changed files with 472 additions and 240 deletions

View File

@ -11,6 +11,10 @@ if(WIN32)
WinMM AdvAPI32 Ole32 ShLwApi Vfw32 WinInet PsApi
DDraw DxGuid DSound DInput8)
else()
# Pthreads
set(sys_libs ${sys_libs}
pthread)
# Allegro library on Unix like systems
find_program(allegro_config "allegro-config")

View File

@ -8,4 +8,5 @@ add_library(base-lib
mutex.cpp
path.cpp
split_string.cpp
string.cpp)
string.cpp
thread.cpp)

149
src/base/thread.cpp Normal file
View File

@ -0,0 +1,149 @@
// ASE gui library
// Copyright (C) 2001-2010 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#include "config.h"
#include "base/thread.h"
#ifdef WIN32
#include <windows.h>
#include <process.h>
#else
#include <pthread.h> // Use pthread library in Unix-like systems
#endif
namespace {
#ifdef WIN32
static DWORD WINAPI win32_thread_proxy(LPVOID data)
{
base::thread::details::thread_proxy(data);
return 0;
}
#else
static void* pthread_thread_proxy(void* data)
{
base::thread::details::thread_proxy(data);
return NULL;
}
#endif
}
base::thread::thread()
: m_native_handle(NULL)
, m_id()
{
}
base::thread::~thread()
{
if (joinable())
detach();
#ifdef USE_PTHREADS
delete (pthread_t*)m_native_handle;
#endif
}
bool base::thread::joinable() const
{
return (m_id != this_thread::get_id());
}
void base::thread::join()
{
if (joinable()) {
#ifdef WIN32
::WaitForSingleObject(m_native_handle, INFINITE);
#else
::pthread_join(*(pthread_t*)m_native_handle, NULL);
#endif
detach();
}
}
void base::thread::detach()
{
if (joinable()) {
#ifdef WIN32
::CloseHandle(m_native_handle);
#else
::pthread_detach(*(pthread_t*)m_native_handle);
delete (pthread_t*)m_native_handle;
#endif
m_native_handle = NULL;
m_id = id();
}
}
void base::thread::launch_thread(func_wrapper* f)
{
#ifdef WIN32
DWORD native_id;
m_native_handle = ::CreateThread(NULL, 0, win32_thread_proxy, (LPVOID)f,
CREATE_SUSPENDED, &native_id);
m_id.m_native_id = native_id;
ResumeThread(m_native_handle);
#else
pthread_t thread;
if (::pthread_create(&thread, NULL, pthread_thread_proxy, f))
m_native_handle = new pthread_t(thread);
else
m_native_handle = NULL;
#endif
}
void base::thread::details::thread_proxy(void* data)
{
func_wrapper* f = reinterpret_cast<func_wrapper*>(data);
// Call operator() of func_wrapper class (this is a virtual method).
(*f)();
// Delete the data (it was created in the thread() ctor).
delete f;
}
base::thread::id base::thread::details::get_current_thread_id()
{
#ifdef WIN32
return id(::GetCurrentThreadId());
#else
return id((unsigned int)::pthread_self());
#endif
}
base::thread::id base::this_thread::get_id()
{
return thread::details::get_current_thread_id();
}
void base::this_thread::yield()
{
#ifdef WIN32
::Sleep(0);
#else
// TODO
#endif
}
void base::this_thread::sleep_for(int milliseconds)
{
#ifdef WIN32
::Sleep(milliseconds);
#else
// TODO
#endif
}

141
src/base/thread.h Normal file
View File

@ -0,0 +1,141 @@
// ASE base library
// Copyright (C) 2001-2010 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#ifndef GUI_THREAD_H_INCLUDED
#define GUI_THREAD_H_INCLUDED
namespace base { // Based on C++0x threads lib
class thread {
public:
class details;
class id
{
friend class thread;
friend class details;
unsigned int m_native_id;
id(unsigned int id) : m_native_id(id) { }
public:
id() : m_native_id(0) { }
bool operator==(const id& y) const { return m_native_id == y.m_native_id; }
bool operator!=(const id& y) const { return m_native_id != y.m_native_id; }
bool operator< (const id& y) const { return m_native_id < y.m_native_id; }
bool operator<=(const id& y) const { return m_native_id <= y.m_native_id; }
bool operator> (const id& y) const { return m_native_id > y.m_native_id; }
bool operator>=(const id& y) const { return m_native_id >= y.m_native_id; }
// TODO should we replace this with support for iostreams?
//unsigned get_native_id() { return m_native_id; }
};
typedef void* native_handle_type;
public:
// Create an instance to represent the current thread
thread();
// Create a new thread without arguments
template<class Callable>
thread(const Callable& f) {
launch_thread(new func_wrapper0<Callable>(f));
}
// Create a new thread with one argument
template<class Callable, class A>
thread(const Callable& f, A a) {
launch_thread(new func_wrapper1<Callable, A>(f, a));
}
// Create a new thread with two arguments
template<class Callable, class A, class B>
thread(const Callable& f, A a, B b) {
launch_thread(new func_wrapper2<Callable, A, B>(f, a, b));
}
~thread();
bool joinable() const;
void join();
void detach();
id get_id() const {
return m_id;
}
native_handle_type native_handle() {
return m_native_handle;
}
class details {
public:
static void thread_proxy(void* data);
static id get_current_thread_id();
};
private:
native_handle_type m_native_handle;
id m_id;
class func_wrapper {
public:
virtual void operator()() = 0;
};
void launch_thread(func_wrapper* f);
template<class Callable>
class func_wrapper0 : public func_wrapper {
public:
Callable f;
func_wrapper0(const Callable& f) : f(f) { }
void operator()() { f(); }
};
template<class Callable, class A>
class func_wrapper1 : public func_wrapper {
public:
Callable f;
A a;
func_wrapper1(const Callable& f, A a) : f(f), a(a) { }
void operator()() { f(a); }
};
template<class Callable, class A, class B>
class func_wrapper2 : public func_wrapper {
public:
Callable f;
A a;
B b;
func_wrapper2(const Callable& f, A a, B b) : f(f), a(a), b(b) { }
void operator()() { f(a, b); }
};
};
namespace this_thread
{
thread::id get_id();
void yield();
void sleep_for(int milliseconds);
}
// This class joins the thread in its destructor.
class thread_guard {
thread& m_thread;
public:
explicit thread_guard(thread& t) : m_thread(t) { }
~thread_guard()
{
if (m_thread.joinable())
m_thread.join();
}
};
}
#endif

View File

@ -0,0 +1,56 @@
// ASE base library
// Copyright (C) 2001-2010 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#include "tests/test.h"
#include "base/thread.h"
#include <iostream>
using std::cout;
using namespace base;
static bool flag = false;
static void func0() {
flag = true;
}
static void func1(int x) {
flag = true;
EXPECT_EQ(2, x);
}
static void func2(int x, int y) {
flag = true;
EXPECT_EQ(2, x);
EXPECT_EQ(4, y);
}
TEST(Thread, WithoutArgs)
{
flag = false;
thread t(&func0);
t.join();
EXPECT_TRUE(flag);
}
TEST(Thread, OneArg)
{
flag = false;
thread t(&func1, 2);
t.join();
EXPECT_TRUE(flag);
}
TEST(Thread, TwoArgs)
{
flag = false;
thread t(&func2, 2, 4);
t.join();
EXPECT_TRUE(flag);
}

View File

@ -18,22 +18,22 @@
#include "config.h"
#include <stdio.h>
#include <allegro.h>
#include <stdio.h>
#include "gui/jinete.h"
#include "ui_context.h"
#include "app.h"
#include "base/thread.h"
#include "commands/command.h"
#include "commands/params.h"
#include "console.h"
#include "app.h"
#include "dialogs/filesel.h"
#include "file/file.h"
#include "raster/sprite.h"
#include "gui/jinete.h"
#include "modules/editors.h"
#include "modules/gui.h"
#include "raster/sprite.h"
#include "recent_files.h"
#include "ui_context.h"
#include "widgets/statebar.h"
//////////////////////////////////////////////////////////////////////
@ -57,7 +57,6 @@ struct OpenFileData
Monitor *monitor;
FileOp *fop;
Progress *progress;
JThread thread;
Frame* alert_window;
};
@ -66,10 +65,8 @@ struct OpenFileData
*
* [loading thread]
*/
static void openfile_bg(void *fop_data)
static void openfile_bg(FileOp* fop)
{
FileOp* fop = (FileOp*)fop_data;
try {
fop_operate(fop);
}
@ -160,55 +157,48 @@ void OpenFileCommand::onExecute(Context* context)
unrecent = true;
}
else {
JThread thread = jthread_new(openfile_bg, fop);
if (thread) {
OpenFileData* data = new OpenFileData;
base::thread thread(&openfile_bg, fop);
OpenFileData* data = new OpenFileData;
data->fop = fop;
data->progress = app_get_statusbar()->addProgress();
data->thread = thread;
data->alert_window = jalert_new(PACKAGE
"<<Loading file:<<%s||&Cancel",
get_filename(m_filename.c_str()));
data->fop = fop;
data->progress = app_get_statusbar()->addProgress();
data->alert_window = jalert_new(PACKAGE
"<<Loading file:<<%s||&Cancel",
get_filename(m_filename.c_str()));
// Add a monitor to check the loading (FileOp) progress
data->monitor = add_gui_monitor(monitor_openfile_bg,
monitor_free, data);
// Add a monitor to check the loading (FileOp) progress
data->monitor = add_gui_monitor(monitor_openfile_bg,
monitor_free, data);
data->alert_window->open_window_fg();
data->alert_window->open_window_fg();
if (data->monitor != NULL)
remove_gui_monitor(data->monitor);
if (data->monitor != NULL)
remove_gui_monitor(data->monitor);
// Stop the file-operation and wait the thread to exit
fop_stop(data->fop);
jthread_join(data->thread);
// Stop the file-operation and wait the thread to exit
fop_stop(data->fop);
thread.join();
// Show any error
if (fop->error)
console.printf(fop->error);
// Show any error
if (fop->error)
console.printf(fop->error);
Sprite *sprite = fop->sprite;
if (sprite) {
UIContext* context = UIContext::instance();
Sprite *sprite = fop->sprite;
if (sprite) {
UIContext* context = UIContext::instance();
App::instance()->getRecentFiles()->addRecentFile(fop->filename);
context->add_sprite(sprite);
App::instance()->getRecentFiles()->addRecentFile(fop->filename);
context->add_sprite(sprite);
set_sprite_in_more_reliable_editor(sprite);
}
else if (!fop_is_stop(fop))
unrecent = true;
delete data->progress;
jwidget_free(data->alert_window);
fop_free(fop);
delete data;
}
else {
console.printf("Error creating thread to load the sprite");
fop_free(fop);
set_sprite_in_more_reliable_editor(sprite);
}
else if (!fop_is_stop(fop))
unrecent = true;
delete data->progress;
jwidget_free(data->alert_window);
fop_free(fop);
delete data;
}
// The file was not found or was loaded loaded with errors,

View File

@ -20,25 +20,24 @@
#include <allegro.h>
#include "gui/jinete.h"
#include "commands/command.h"
#include "app.h"
#include "base/thread.h"
#include "commands/command.h"
#include "console.h"
#include "dialogs/filesel.h"
#include "file/file.h"
#include "gui/jinete.h"
#include "modules/gui.h"
#include "recent_files.h"
#include "raster/sprite.h"
#include "widgets/statebar.h"
#include "recent_files.h"
#include "sprite_wrappers.h"
#include "widgets/statebar.h"
typedef struct SaveFileData
{
Monitor *monitor;
FileOp *fop;
Progress *progress;
JThread thread;
Frame* alert_window;
} SaveFileData;
@ -95,54 +94,52 @@ static void monitor_free(void *_data)
static void save_sprite_in_background(Sprite* sprite, bool mark_as_saved)
{
FileOp *fop = fop_to_save_sprite(sprite);
if (fop) {
JThread thread = jthread_new(savefile_bg, fop);
if (thread) {
SaveFileData* data = new SaveFileData;
if (!fop)
return;
data->fop = fop;
data->progress = app_get_statusbar()->addProgress();
data->thread = thread;
data->alert_window = jalert_new(PACKAGE
"<<Saving file:<<%s||&Cancel",
get_filename(sprite->getFilename()));
base::thread thread(&savefile_bg, fop);
SaveFileData* data = new SaveFileData;
/* add a monitor to check the saving (FileOp) progress */
data->monitor = add_gui_monitor(monitor_savefile_bg,
monitor_free, data);
data->fop = fop;
data->progress = app_get_statusbar()->addProgress();
data->alert_window = jalert_new(PACKAGE
"<<Saving file:<<%s||&Cancel",
get_filename(sprite->getFilename()));
/* TODO error handling */
/* add a monitor to check the saving (FileOp) progress */
data->monitor = add_gui_monitor(monitor_savefile_bg,
monitor_free, data);
data->alert_window->open_window_fg();
/* TODO error handling */
if (data->monitor != NULL)
remove_gui_monitor(data->monitor);
data->alert_window->open_window_fg();
/* wait the `savefile_bg' thread */
jthread_join(data->thread);
if (data->monitor != NULL)
remove_gui_monitor(data->monitor);
/* show any error */
if (fop->error) {
Console console;
console.printf(fop->error);
}
/* no error? */
else {
App::instance()->getRecentFiles()->addRecentFile(sprite->getFilename());
if (mark_as_saved)
sprite->markAsSaved();
/* wait the `savefile_bg' thread */
thread.join();
app_get_statusbar()
->setStatusText(2000, "File %s, saved.",
get_filename(sprite->getFilename()));
}
delete data->progress;
jwidget_free(data->alert_window);
fop_free(fop);
delete data;
}
/* show any error */
if (fop->error) {
Console console;
console.printf(fop->error);
}
/* no error? */
else {
App::instance()->getRecentFiles()->addRecentFile(sprite->getFilename());
if (mark_as_saved)
sprite->markAsSaved();
app_get_statusbar()
->setStatusText(2000, "File %s, saved.",
get_filename(sprite->getFilename()));
}
delete data->progress;
jwidget_free(data->alert_window);
fop_free(fop);
delete data;
}
/*********************************************************************/

View File

@ -21,13 +21,13 @@
#include <stdlib.h>
#include <string.h>
#include "gui/jinete.h"
#include "app.h"
#include "base/mutex.h"
#include "base/scoped_lock.h"
#include "base/thread.h"
#include "core/cfg.h"
#include "effect/effect.h"
#include "gui/jinete.h"
#include "modules/editors.h"
#include "modules/gui.h"
#include "raster/sprite.h"
@ -48,9 +48,8 @@ struct ThreadData
float pos; /* current progress position */
bool done : 1; /* was the effect completelly applied? */
bool cancelled : 1; /* was the effect cancelled by the user? */
Monitor *monitor; /* monitor to update the progress-bar */
Progress *progress; /* the progress-bar */
JThread thread; /* thread to apply the effect in background */
Monitor* monitor; /* monitor to update the progress-bar */
Progress* progress; /* the progress-bar */
Frame* alert_window; /* alert for the user to cancel the
effect-progress if he wants */
};
@ -91,10 +90,8 @@ static bool effect_is_cancelled_hook(void *_data)
*
* [effect thread]
*/
static void effect_bg(void *_data)
static void effect_bg(ThreadData* data)
{
ThreadData *data = (ThreadData *)_data;
/* apply the effect */
effect_apply_to_target(data->effect);
@ -160,14 +157,15 @@ void effect_apply_to_target_with_progressbar(Effect* effect)
data->done = false;
data->cancelled = false;
data->progress = app_get_statusbar()->addProgress();
data->thread = jthread_new(effect_bg, data);
data->alert_window = jalert_new(PACKAGE
"<<Applying effect...||&Cancel");
data->monitor = add_gui_monitor(monitor_effect_bg,
monitor_free, data);
/* TODO error handling */
// Launch the thread to apply the effect in background
base::thread thread(&effect_bg, data);
// Open the alert window in foreground (this is modal, locks the main thread)
data->alert_window->open_window_fg();
{
@ -178,8 +176,8 @@ void effect_apply_to_target_with_progressbar(Effect* effect)
}
}
/* wait the `effect_bg' thread */
jthread_join(data->thread);
// Wait the `effect_bg' thread
thread.join();
delete data->progress;
jwidget_free(data->alert_window);

View File

@ -19,11 +19,9 @@
#include "tests/test.h"
#include <errno.h>
#include "gui/jthread.h"
#include "base/thread.h"
static JThread thread;
static void run_thread(void *data)
static void run_thread()
{
errno = 0;
EXPECT_EQ(0, errno);
@ -36,8 +34,8 @@ TEST(Errno, ThreadSafe)
// Run another thread that will be modify the errno variable, and
// wait it (join).
thread = jthread_new(run_thread, NULL);
jthread_join(thread);
base::thread thr(&run_thread);
thr.join();
// See if errno was not modified in this thread.
EXPECT_EQ(33, errno);

View File

@ -39,7 +39,6 @@ add_library(gui-lib
jsystem.cpp
jtextbox.cpp
jtheme.cpp
jthread.cpp
jtooltips.cpp
jview.cpp
label.cpp

View File

@ -39,7 +39,6 @@
#include "gui/jsystem.h"
#include "gui/jtextbox.h"
#include "gui/jtheme.h"
#include "gui/jthread.h"
#include "gui/jtooltips.h"
#include "gui/jview.h"
#include "gui/widget.h"

View File

@ -1,86 +0,0 @@
// ASE gui library
// Copyright (C) 2001-2010 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#include "config.h"
#include <allegro.h>
#include "gui/jthread.h"
#if defined ALLEGRO_WINDOWS
#include <winalleg.h>
#include <process.h>
#elif defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX
#include <pthread.h>
#endif
struct pthread_proxy_data {
void (*proc)(void*);
void* data;
};
#if defined ALLEGRO_WINDOWS
static DWORD WINAPI pthread_proxy(void* arg)
{
pthread_proxy_data* ptr = reinterpret_cast<pthread_proxy_data*>(arg);
void (*proc)(void*) = ptr->proc;
void* data = ptr->data;
jfree(ptr);
(*proc)(data);
return 0;
}
#elif defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX
static void* pthread_proxy(void* arg)
{
pthread_proxy_data* ptr = reinterpret_cast<pthread_proxy_data*>(arg);
void (*proc)(void*) = ptr->proc;
void* data = ptr->data;
jfree(ptr);
(*proc)(data);
return NULL;
}
#endif
JThread jthread_new(void (*proc)(void*), void* data)
{
struct pthread_proxy_data *ptr = jnew(struct pthread_proxy_data, 1);
ptr->proc = proc;
ptr->data = data;
#if defined ALLEGRO_WINDOWS
DWORD id;
HANDLE thread = CreateThread(NULL, 0, pthread_proxy, ptr,
CREATE_SUSPENDED, &id);
if (thread)
ResumeThread(thread);
return thread;
#elif defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX
pthread_t thread = 0;
if (pthread_create(&thread, NULL, pthread_proxy, ptr))
return NULL;
else
return (JThread)thread;
#else
#error ASE does not support threads for your platform
#endif
}
void jthread_join(JThread thread)
{
#if defined ALLEGRO_WINDOWS
WaitForSingleObject(thread, INFINITE);
#elif defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX
pthread_join((pthread_t)thread, NULL);
#else
#error ASE does not support threads for your platform
#endif
}

View File

@ -1,15 +0,0 @@
// ASE gui library
// Copyright (C) 2001-2010 David Capello
//
// This source file is ditributed under a BSD-like license, please
// read LICENSE.txt for more information.
#ifndef GUI_JTHREAD_H_INCLUDED
#define GUI_JTHREAD_H_INCLUDED
#include "gui/jbase.h"
JThread jthread_new(void (*proc)(void *data), void *data);
void jthread_join(JThread thread);
#endif

View File

@ -18,14 +18,13 @@
#include "config.h"
#include "gui/jalert.h"
#include "gui/jthread.h"
#include "gui/widget.h"
#include "gui/frame.h"
#include "app.h"
#include "base/mutex.h"
#include "base/scoped_lock.h"
#include "base/thread.h"
#include "gui/frame.h"
#include "gui/jalert.h"
#include "gui/widget.h"
#include "job.h"
#include "modules/gui.h"
#include "widgets/statebar.h"
@ -63,8 +62,10 @@ Job::~Job()
m_monitor = NULL;
}
if (m_thread)
jthread_join(m_thread);
if (m_thread) {
m_thread->join();
delete m_thread;
}
if (m_progress)
delete m_progress;
@ -78,7 +79,7 @@ Job::~Job()
void Job::startJob()
{
m_thread = jthread_new(&Job::thread_proc, (void*)this);
m_thread = new base::thread(&Job::thread_proc, this);
m_alert_window->open_window_fg();
}
@ -133,9 +134,8 @@ void Job::done()
*
* [worker thread]
*/
void Job::thread_proc(void* data)
void Job::thread_proc(Job* self)
{
Job* self = (Job*)data;
try {
self->onJob();
}

View File

@ -19,7 +19,7 @@
#ifndef CORE_JOB_H_INCLUDED
#define CORE_JOB_H_INCLUDED
#include "gui/jbase.h"
namespace base { class thread; }
class Frame;
class Mutex;
@ -63,11 +63,11 @@ protected:
private:
void done();
static void thread_proc(void* data);
static void thread_proc(Job* self);
static void monitor_proc(void* data);
static void monitor_free(void* data);
JThread m_thread;
base::thread* m_thread;
Monitor* m_monitor;
Progress* m_progress;
Mutex* m_mutex;

View File

@ -22,6 +22,7 @@
#include <allegro.h>
#include "app.h"
#include "base/thread.h"
#include "commands/commands.h"
#include "console.h"
#include "dialogs/filesel.h"
@ -69,7 +70,7 @@ typedef struct ThumbnailData
IFileItem* fileitem;
JWidget fileview;
Image* thumbnail;
JThread thread;
base::thread* thread;
Palette* palette;
} ThumbnailData;
@ -84,7 +85,7 @@ static void fileview_generate_preview_of_selected_item(JWidget widget);
static bool fileview_generate_thumbnail(JWidget widget, IFileItem* fileitem);
static void fileview_stop_threads(FileView* fileview);
static void openfile_bg(void *data);
static void openfile_bg(ThumbnailData* data);
static void monitor_thumbnail_generation(void *data);
static void monitor_free_thumbnail_generation(void *data);
@ -724,7 +725,7 @@ static bool fileview_generate_thumbnail(JWidget widget, IFileItem* fileitem)
data->fileview = widget;
data->thumbnail = NULL;
data->thread = jthread_new(openfile_bg, data);
data->thread = new base::thread(&openfile_bg, data);
if (data->thread) {
// add a monitor to check the loading (FileOp) progress
data->monitor = add_gui_monitor(monitor_thumbnail_generation,
@ -766,10 +767,9 @@ static void fileview_stop_threads(FileView* fileview)
*
* [loading thread]
*/
static void openfile_bg(void *_data)
static void openfile_bg(ThumbnailData* data)
{
ThumbnailData *data = (ThumbnailData *)_data;
FileOp *fop = (FileOp *)data->fop;
FileOp *fop = (FileOp*)data->fop;
Sprite *sprite;
int thumb_w, thumb_h;
Image *image;
@ -820,8 +820,8 @@ static void openfile_bg(void *_data)
*/
static void monitor_thumbnail_generation(void *_data)
{
ThumbnailData *data = (ThumbnailData *)_data;
FileOp *fop = (FileOp *)data->fop;
ThumbnailData* data = (ThumbnailData*)_data;
FileOp* fop = data->fop;
/* is done? ...ok, now the thumbnail is in the main thread only... */
if (fop_is_done(fop)) {
@ -859,11 +859,12 @@ static void monitor_thumbnail_generation(void *_data)
*/
static void monitor_free_thumbnail_generation(void *_data)
{
ThumbnailData *data = (ThumbnailData *)_data;
FileOp *fop = (FileOp *)data->fop;
ThumbnailData *data = (ThumbnailData*)_data;
FileOp *fop = data->fop;
fop_stop(fop);
jthread_join(data->thread);
data->thread->join();
delete data->thread;
// remove the monitor from the list
MonitorList& monitors(fileview_data(data->fileview)->monitors);