【发布时间】: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