You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何等待GTK窗口的pthread关闭?批量下载进度窗异常排查

问题分析与修复

核心问题

  1. 全局变量滥用:progress_bar、window、new_handle均为全局变量,循环调用downloadFile时,后续调用会覆盖这些变量的内容,导致前一次下载的进度窗口控件被覆盖,无法正常更新。
  2. 主线程被强制阻塞:downloadFile中创建progressDialog线程后立刻调用pthread_join,迫使主线程等待当前下载完成才进入下一次循环,这是所有下载完成后才批量弹出窗口的直接原因。
  3. GTK事件循环缺失:progressDialog中注释了gtk_main(),GTK未启动主事件循环,无法处理窗口绘制和事件更新,导致窗口无法及时显示。
  4. GTK重复初始化:每次调用progressDialog都执行gtk_init,GTK仅允许初始化一次,多次调用会引发不可预期的错误。
  5. 跨线程操作GTK控件:progress_func在libcurl的回调线程中直接操作GTK进度条,违反了GTK控件必须在主循环线程操作的规则,可能导致界面卡顿或崩溃。

修复方案

1. 封装下载与窗口数据为结构体

将每个下载任务的curl句柄、进度条、窗口等数据封装到专属结构体中,彻底消除全局变量冲突:

typedef struct {
    CURL* curl_handle;
    GtkWidget* window;
    GtkWidget* progress_bar;
    FILE* output_file;
    char* download_url;
    char* output_path;
    double total_size;
    double downloaded_size;
} DownloadTask;

2. 重构线程逻辑,避免主线程阻塞

  • 不在downloadFile中调用pthread_join,让每个下载任务的线程独立运行。
  • 程序入口仅初始化一次GTK并启动全局主循环,所有窗口创建和控件操作都在主循环线程中处理。

3. 线程安全更新进度条

通过g_idle_add将进度更新操作投递到GTK主循环线程执行,避免跨线程操作控件:

static gboolean update_progress_bar(gpointer data) {
    DownloadTask* task = (DownloadTask*)data;
    if (task->total_size > 0) {
        double progress = task->downloaded_size / task->total_size;
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(task->progress_bar), progress);
        char text_buf[64];
        snprintf(text_buf, sizeof(text_buf), "%.1f%%", progress * 100);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(task->progress_bar), text_buf);
    }
    return FALSE;
}

int progress_func(void *ptr, double total, double downloaded, double ultotal, double ulnow) {
    DownloadTask* task = (DownloadTask*)ptr;
    task->total_size = total;
    task->downloaded_size = downloaded;
    g_idle_add(update_progress_bar, task);
    return 0;
}

修复后的完整简化代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <curl/curl.h>
#include <pthread.h>

typedef struct {
    CURL* curl_handle;
    GtkWidget* window;
    GtkWidget* progress_bar;
    FILE* output_file;
    char* download_url;
    char* output_path;
    double total_size;
    double downloaded_size;
} DownloadTask;

static size_t write_data(void* data, size_t size, size_t nmemb, void* userdata) {
    DownloadTask* task = (DownloadTask*)userdata;
    return fwrite(data, size, nmemb, task->output_file);
}

static gboolean update_progress_bar(gpointer data) {
    DownloadTask* task = (DownloadTask*)data;
    if (task->total_size > 0) {
        double progress = task->downloaded_size / task->total_size;
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(task->progress_bar), progress);
        char text_buf[64];
        snprintf(text_buf, sizeof(text_buf), "%.1f%%", progress * 100);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(task->progress_bar), text_buf);
    }
    return FALSE;
}

int progress_func(void *ptr, double total, double downloaded, double ultotal, double ulnow) {
    DownloadTask* task = (DownloadTask*)ptr;
    task->total_size = total;
    task->downloaded_size = downloaded;
    g_idle_add(update_progress_bar, task);
    return 0;
}

void* download_task_thread(void* arg) {
    DownloadTask* task = (DownloadTask*)arg;

    // 初始化CURL句柄
    task->curl_handle = curl_easy_init();
    curl_easy_setopt(task->curl_handle, CURLOPT_URL, task->download_url);
    curl_easy_setopt(task->curl_handle, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(task->curl_handle, CURLOPT_WRITEDATA, task);
    curl_easy_setopt(task->curl_handle, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(task->curl_handle, CURLOPT_PROGRESSFUNCTION, progress_func);
    curl_easy_setopt(task->curl_handle, CURLOPT_PROGRESSDATA, task);
    curl_easy_setopt(task->curl_handle, CURLOPT_FAILONERROR, 1L);

    // 执行下载
    CURLcode res = curl_easy_perform(task->curl_handle);
    if (res != CURLE_OK) {
        fprintf(stderr, "下载失败: %s\n", curl_easy_strerror(res));
    }

    // 清理资源
    curl_easy_cleanup(task->curl_handle);
    fclose(task->output_file);
    g_idle_add((GSourceFunc)gtk_widget_destroy, task->window);
    free(task->download_url);
    free(task->output_path);
    free(task);
    return NULL;
}

gboolean create_progress_window(gpointer data) {
    DownloadTask* task = (DownloadTask*)data;

    // 创建进度窗口
    task->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(task->window), "下载进度");
    gtk_window_set_default_size(GTK_WINDOW(task->window), 300, 50);
    gtk_container_set_border_width(GTK_CONTAINER(task->window), 10);

    // 创建进度条
    task->progress_bar = gtk_progress_bar_new();
    gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(task->progress_bar), TRUE);
    gtk_progress_bar_set_text(GTK_PROGRESS_BAR(task->progress_bar), "准备下载");

    // 窗口布局
    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_container_add(GTK_CONTAINER(task->window), box);
    gtk_box_pack_start(GTK_BOX(box), task->progress_bar, TRUE, TRUE, 0);

    gtk_widget_show_all(task->window);
    return FALSE;
}

int downloadFile(char *download_url, char *output_path) {
    DownloadTask* task = malloc(sizeof(DownloadTask));
    task->download_url = strdup(download_url);
    task->output_path = strdup(output_path);
    task->total_size = 0;
    task->downloaded_size = 0;

    // 打开输出文件
    task->output_file = fopen(output_path, "wb");
    if (!task->output_file) {
        fprintf(stderr, "无法打开文件: %s\n", output_path);
        free(task->download_url);
        free(task->output_path);
        free(task);
        return -1;
    }

    // 在GTK主线程创建进度窗口
    g_idle_add(create_progress_window, task);

    // 创建下载线程并分离
    pthread_t thread;
    if (pthread_create(&thread, NULL, download_task_thread, task) != 0) {
        fprintf(stderr, "创建线程失败\n");
        fclose(task->output_file);
        free(task->download_url);
        free(task->output_path);
        free(task);
        return -1;
    }
    pthread_detach(thread);
    return 0;
}

int main(int argc, char** argv) {
    gtk_init(&argc, &argv);
    curl_global_init(CURL_GLOBAL_ALL);

    // 示例:循环调用下载
    downloadFile("http://example.com/file1.zip", "file1.zip");
    downloadFile("http://example.com/file2.zip", "file2.zip");

    gtk_main();
    curl_global_cleanup();
    return 0;
}

关键修复总结

  • 用结构体封装每个下载任务的专属数据,彻底消除全局变量冲突。
  • 主线程不阻塞,每个下载任务启动独立线程,窗口随任务实时创建。
  • 所有GTK控件操作通过g_idle_add投递到主循环线程,遵守GTK线程安全规则。
  • 仅初始化一次GTK和libcurl,避免重复初始化引发的错误。

内容的提问来源于stack exchange,提问作者Xpl0itU

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 14:30:39