簡體   English   中英

如何在更新 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 調用的GTask ( data_acq data_acq() )長時間運行,我有一個更新的進度條(通過gdk_threads_add_timeout(progressBar_timeout_cb) ),但在 data_acq( data_acq()結束時,我使用gdk_threads_add_idle(progressBar_complete, params)來設置進度條到 100%。 問題出現在GTask的最后一步是自動調用free_data_acq_data(params)釋放指向進度條的指針,如果在free_data_acq_data(params)發生之后發生,則在progressBar_complete(params)觸發時會導致段錯誤。

這個問題可能完全是由於我濫用指針(或其他一些新手錯誤),但我承認我沒有看到如何以保證 1)它會在使用后被釋放的方式傳遞數據並且2)它不會被過早釋放。 請注意,任務可能被取消,因此我不想在progressBar_complete()中釋放 memory 。

所以我的問題是:在這種情況下是否有更好的方法來避免競爭條件(或者是否有另一種方法來構造代碼以避免這個問題)? 或者是否有檢查gdk_threads_add_idle() function 是否已完成的機制,所以我可以告訴釋放內存的 function 等到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()排隊另一個 function ,這將釋放 UI 端使用的資源,如dataAcqParams ,而不是在工作線程中釋放它?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM