如何避免在更新 GTK UI 的 GTask 结束时出现竞争条件?

How do I avoid a race condition at the end of a GTask that updates a GTK UI?

我正在构建一个 gtk3 应用程序,并试图找出在更新 UI.

GTask 完成时避免竞争条件的方法

由于 GTask 调用的函数 (data_acq()) 是 long-运行ning,我有一个更新的进度条(通过 gdk_threads_add_timeout(progressBar_timeout_cb)),但是在 data_acq() 结束时,我使用 gdk_threads_add_idle(progressBar_complete, params) 将进度条设置为 100%。问题出现在 GTask 的最后一步是自动调用 free_data_acq_data(params) 释放指向进度条的指针,当 progressBar_complete(params) 触发时导致段错误,如果它发生 free_data_acq_data(params) 发生后。

这个问题可能完全是由于我滥用指针(或其他一些菜鸟错误)造成的,但我承认我没有看到如何以保证 1) 的方式传递数据在使用后被释放,并且 2) 它不会被释放得太早。请注意,任务有可能被取消,所以我不想释放 progressBar_complete().

中的内存

所以我的问题是:在这种情况下是否有更好的方法来避免竞争条件(或者是否有另一种方法来构建代码以避免此问题)?或者是否有一种机制可以检查 gdk_threads_add_idle() 函数是否已完成,以便我可以告诉内存释放函数等待 idle 完成?

我尝试使用全局来跟踪 progressBar_complete 是否有 运行,但我无法让它与 GTask 很好地配合,因为这意味着 free_data_acq_data 从未完成(因为线程从未放弃 progressBar_complete 以更新全局)。

为了具体起见,这里有一些示例代码:

#include <gtk/gtk.h>

struct dataAcqParams {
  GtkWidget *progressBar;
  int timeoutID;
  GCancellable *cancellable;
};


void update_progressBar(GtkWidget *progressBar,
                        double fraction)
{
  gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressBar), fraction);
}

int complete_progressBar(gpointer data)
{
  struct dataAcqParams *params = data;
  GtkWidget *progressBar;
  progressBar = params->progressBar;
  g_source_remove(params->timeoutID); // This turns off automatic progress bar updates
  update_progressBar(progressBar, 1.0);

  return G_SOURCE_REMOVE;
}

int progressBar_timeout_cb(gpointer data)
{
  GtkWidget *progressBar;
  progressBar = params->progressBar;

  double fraction = 0.50; // For brevity
  update_progressBar(progressBar, fraction);

  return G_SOURCE_CONTINUE; // We keep calling this until we cancel it...
}

void free_data_acq_data(void *data)
{
  struct dataAcqParams *params = data;
  g_free(params);
}

int data_acq(struct dataAcqParams *data)
{
  struct dataAcqParams *params = data;

  g_print("Performing a measurement...\n");
  g_usleep(10e5); // Some long computation, can be cancelled
    
  gdk_threads_add_idle(complete_progressBar, params); // Race condition starts here
  g_usleep(500000); // Delay to prevent race condition, surely there is a better way?

  return 0;
}

static void data_acq_cb(GTask    *task,
                        gpointer source_object,
                        gpointer task_data,
                        GCancellable *cancellable)
{
  struct dataAcqParams *params = task_data;
  int retval;

  // Handle Cancellation:
  if(g_task_return_error_if_cancelled(task))
  {
    return;
  }

  retval = data_acq(params);

  g_task_return_int(task, retval);
}

void start_data_acq_async(gpointer            data,
                          GCancellable       *cancellable,
                          GAsyncReadyCallback callback,
                          gpointer            user_data)
{

  GTask *task = NULL;
  struct dataAcqParams *params;
  params = (struct dataAcqParams *) data;

  // Error if this is badly formatted:
  g_return_if_fail(cancellable == NULL | G_IS_CANCELLABLE(cancellable));

  task = g_task_new(NULL, cancellable, callback, user_data);
  g_task_set_source_tag(task, start_data_acq_async);

  g_task_set_return_on_cancel(task, FALSE);

  g_task_set_task_data(task, params, free_data_acq_data);

  // Run the acquisition in a worker thread:
  g_task_run_in_thread(task, data_acq_cb);

  g_object_unref(task);
}

int main(int argc, char **argv)
{
  gtk_init(&argc, &argv);
  
  // ... build/show ui ...
  GtkWidget *progressBar;
  GtkBuilder *builder;
  // ...

  progressBar = GTK_WIDGET(gtk_builder_get_object(builder, "progress_bar"));
  GCancellable *cancellable; 
 
  struct dataAcqParams *params = g_malloc(sizeof(*params));

  params->progressBar = progressBar;
  params->cancellable = cancellable;
  params->timeoutID = gdk_threads_add_timeout(100, progressBar_timeout_cb,
                                        params);

  start_data_acq_async(params, cancellable, NULL, NULL);

  gtk_main();

  return 0;
}

如果您需要从UI线程访问进度条指针,那么我建议不要在工作线程中释放它。也许当任务完成或取消时,您可以使用 gdk_threads_add_idle() 排队另一个函数,该函数将像 dataAcqParams 一样释放 UI 端使用的资源,而不是在 worker 中释放它线程?