How to cancel infinite task properly?

, ,

Hello. I created a simple function cb that runs infinitely in a thread. It increments and integer value and shows it in a GtkTextView widget. I want to stop the task with a button.
The code I have right now freezes the whole app when the button is pressed and the program becomes unresponsive. What should I change to make the button stop the cb function and the thread it runs in without freezing the whole app?

GtkWidget *dataView;
GTask *task;
GCancellable *cancellable;
GObject *object;

int main (int argc, char *argv[]) {
  //starting application
  return startApp("exe.taskTest", G_APPLICATION_DEFAULT_FLAGS, activateApp, argc, argv);
}
int startApp(char *id, GApplicationFlags flags, void (*activateFunc), int argc, char *argv[]) {
    GtkApplication *app;
    int stat;

    app=gtk_application_new(id, flags);
    g_signal_connect(app, "activate", G_CALLBACK(activateFunc), NULL);
    stat=g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return stat;
}
tatic void activateApp (GApplication *app) {
	
	//let's imagine that we created gtkApplicationWindow here
	//and GtkGrid to store widgets below inside of it
	
	...
	
	//just a part of GtkTextView initialisation
	dataView=gtk_text_view_new();
	GtkTextBuffer *dataViewBuffer;
	dataViewBuffer=gtk_text_buffer_new(NULL);
    gtk_text_view_set_buffer(GTK_TEXT_VIEW(dataView), dataViewBuffer);
	
	...
	
	//just a button initialisation
	GtkWidget *cancelTaskButton;
	cancelTaskButton=initButton("Cancel", cancelTaskCallback, portValues);
	 
	//I skipped over adding those elements to the grid
	... 
	
  //TASK CODE
	  object=g_object_new(G_TYPE_OBJECT, NULL);
	  cancellable=g_cancellable_new();
	  task=g_task_new(object, cancellable, cb, NULL);

	  g_task_set_task_data(task, NULL, cbDestroy);
	  g_task_run_in_thread(task, (void *)cb);

	  g_object_unref(cancellable);
	  g_object_unref(object);
	  g_object_unref(task);
  //TASK CODE END
  
  gtk_window_present(GTK_WINDOW(win));
}
void cb() {

    char infoString[50];
    static int x=0;

    do {
        sprintf(infoString, "%d", x);
        static void *data[2];
            data[0]=dataView;
            data[1]=infoString;
        g_idle_add_once((void *)setText, data);
        g_usleep(1); //needed, otherwise the program crashes
        x++;
    }
    while (1);
}
void cbDestroy() {
    //nothing to free(?)
}
void setText(void* data[]) {
    setTextInTextView(data[0], data[1]);
}
void setTextInTextView(GtkWidget *textView, char *text) {
    GtkTextBuffer *infoBuffer=gtk_text_buffer_new(NULL);
    gtk_text_buffer_set_text(GTK_TEXT_BUFFER(infoBuffer), text, strlen(text));
    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textView), infoBuffer);
}
static void cancelTaskCallback(GtkButton *button, void *data[]) {
    g_task_set_return_on_cancel(task, TRUE);
    g_cancellable_cancel(cancellable);
}	

Hi,

For gracefully exiting the thread, check g_cancellable_is_cancelled() in the infinite loop, and break if true.

2 Likes

Thank you, it works great now!

1 Like

@Inferjus

Side note: if you read input data using a Gio API like g_input_stream_read(), then you can directly pass your Cancellable object to the read function.
It’s a very convenient way to stop blocking APIs like “read”.