【问题标题】:How do I avoid a race condition at the end of a GTask that updates a GTK UI?如何在更新 GTK UI 的 GTask 结束时避免竞争条件?
【发布时间】:2021-01-08 18:46:54
【问题描述】:

我正在构建一个 gtk3 应用程序,并试图避免在完成更新 UI 的 GTask 时出现竞争条件。

由于GTask 调用的函数(data_acq())是长时间运行的,我有一个更新的进度条(通过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) 触发时会导致段错误,如果它发生在之后 @ 987654330@ 发生。

这个问题可能完全是由于我滥用指针(或其他一些新手错误),但我承认我没有看到如何以保证 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;
}

【问题讨论】:

    标签: c multithreading gtk3 glib gdk


    【解决方案1】:

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

    【讨论】:

    • 为了确保我理解你的建议,这基本上删除了 GTask 自动释放内存的功能,因为我自己通过在计算结束时手动释放它来处理它或当它被取消了吗?我相信这会奏效。
    • 没错;如果你不能保证 GTask 会释放“拥有”它的线程中的内存,那么你应该手动释放它。
    猜你喜欢
    • 2019-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 2010-09-25
    • 2019-10-23
    • 1970-01-01
    相关资源
    最近更新 更多