Управление индикатором выполнения нескольких потоков с помощью gtk

в моей программе C загрузите несколько файлов из Интернета, я использую индикатор выполнения GTK, чтобы показать ход загрузки.

я хочу, если я загружу один файл, мое приложение покажет один индикатор выполнения

если я загружаю три файла, мое приложение может отображать три индикатора выполнения. остальное можно сделать таким же образом.

я создаю пользовательский интерфейс с Glade3. GtkTreeView имеет 3 столбца

  1. Имя
  2. Прогресс
  3. Статус

и я написал некоторый код, он может работать, но есть некоторые проблемы

если я скачаю один файл, приложение будет неплохо лонгироваться.

но если я скачаю два файла. приложение не может показать два индикатора выполнения.

приложение просто показывает два потока в одном прогрессе

Как мне это решить?

progress.glade

и исходный код:

/*
gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0` -lcurl -lgthread-2.0  liststore.c -o liststore
 */
#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <unistd.h>
#include <pthread.h>

#include <curl/curl.h>
#include <curl/types.h> /* new for v7 */
#include <curl/easy.h> /* new for v7 */

gchar *URL = "http://soundclash-records.co.uk/mp3s/upfull_rockers_never_gonna_let_you_down.mp3";

size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fwrite(ptr, size, nmemb, stream);
}

size_t my_read_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fread(ptr, size, nmemb, stream);
}

typedef struct _Data Data;
struct _Data
{
    GtkWidget *down; /* Down button */
    GtkWidget *tree; /* Tree view */
    gdouble progress;
};

enum
{
    STRING_COLUMN,
    INT_COLUMN,
    N_COLUMNS
};

gboolean set_download_progress(gpointer data)
{
    Data *treeview = (Data *)data;
    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview->tree)));
    GtkTreeIter iter;

    gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store) ,
                                       &iter,
                                       g_strdup_printf ("%d",0));   
    gtk_list_store_set(store, &iter,
                       INT_COLUMN,treeview->progress, -1);
    
    return FALSE;
}

int my_progress_func(Data *data,
                     double t, /* dltotal */
                     double d, /* dlnow */
                     double ultotal,
                     double ulnow)
{
//  printf("%d / %d (%g %%)\n", d, t, d*100.0/t);
    gdk_threads_enter();
    gdouble progress;
    progress = d*100.0/t;
    data->progress = progress;
    g_idle_add(set_download_progress, data);

    gdk_threads_leave();
    return 0;
}

void *create_thread(void *data)
{
    Data *viewtree = (Data *)data;
    GtkTreeIter iter;
  
    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(viewtree->tree)));
    gtk_list_store_append( store, &iter );
 
    
    g_print("url\n");
    CURL *curl;
    CURLcode res;
    FILE *outfile;
    gchar *url = URL;

    curl = curl_easy_init();
    if(curl)
    {
        outfile = fopen("test.curl", "w");
        if(outfile)
            g_print("curl\n");

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read_func);
        
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, data);

        res = curl_easy_perform(curl);

        fclose(outfile);
        /* always cleanup */
        curl_easy_cleanup(curl);
    }
    g_object_unref( G_OBJECT( store ) );
    return NULL;
}


G_MODULE_EXPORT void
cb_add( GtkWidget *button,
         Data      *data )
{
    if (!g_thread_create(&create_thread, data, FALSE, NULL) != 0)
        g_warning("can't create the thread");
} 
int main(int argc, char **argv)
{
    GtkBuilder *builder;
    GtkWidget  *window;
    Data       *data;


    curl_global_init(CURL_GLOBAL_ALL);
    if( ! g_thread_supported() )
        g_thread_init( NULL );

    gdk_threads_init();
    gtk_init(&argc, &argv);

    data = g_slice_new( Data );

    /* Create builder */
    builder = gtk_builder_new();
    gtk_builder_add_from_file( builder, "progress.glade", NULL );

    window    = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
    data->down   = GTK_WIDGET( gtk_builder_get_object( builder, "down" ) );
    data->tree = GTK_WIDGET( gtk_builder_get_object( builder, "treeview" ) );

    gtk_builder_connect_signals( builder, data );
    g_object_unref( G_OBJECT( builder ) );

    gtk_widget_show( window );

    gtk_main();
    g_slice_free( Data, data );

    return 0;
}

=========================================================

Обновление: 11.12.09

/*
gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0 gthread-2.0 libcurl` liststore2.c -o liststore2
 */
#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <unistd.h>
#include <pthread.h>

#include <curl/curl.h>
#include <curl/types.h> /* new for v7 */
#include <curl/easy.h> /* new for v7 */

gchar *URL = "http://soundclash-records.co.uk/mp3s/upfull_rockers_never_gonna_let_you_down.mp3";
static GHashTable* TreeRowReferences;
static GPrivate* current_data_key = NULL;

size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fwrite(ptr, size, nmemb, stream);
}

size_t my_read_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fread(ptr, size, nmemb, stream);
}

typedef struct _Data Data;
struct _Data
{
    GtkWidget *down; /* Down button */
    GtkWidget *tree; /* Tree view */
    gdouble progress;
};

enum
{
    STRING_COLUMN,
    INT_COLUMN,
    N_COLUMNS
};

gboolean set_download_progress(gpointer data)
{
    Data *treeview = (Data *)data;

    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview->tree)));
    GtkTreeIter iter;

    GtkTreeRowReference* reference = g_hash_table_lookup(TreeRowReferences,data);

    GtkTreePath* path = gtk_tree_row_reference_get_path(reference);

    gtk_tree_model_get_iter(GTK_TREE_MODEL(store), 
                    &iter, path);
                           
    gtk_list_store_set(store, &iter,
                       INT_COLUMN,treeview->progress, -1);

    gtk_tree_path_free (path);
    return FALSE;
}

int my_progress_func(Data *data,
             double t, /* dltotal */
             double d, /* dlnow */
             double ultotal,
             double ulnow)
{
    if(t == 0)
        return 0;
    data->progress = d*100.0/t;

    gdk_threads_enter();
    g_idle_add(set_download_progress, data);
    gdk_threads_leave();
    return 0;
}

void *create_thread(void *data)
{
    
    Data *current_treeview = g_private_get (current_data_key);

    if (!current_treeview)
    {
      current_treeview = g_new (Data, 1);
      current_treeview = (Data *)data;
      g_private_set (current_data_key, current_treeview);
      g_print("p %g\n",current_treeview->progress);
    }
    else{
        current_treeview = (Data *)data;
        g_print("c %g\n",current_treeview->progress);
    }

    g_print("url\n");
    CURL *curl;
    CURLcode res;
    FILE *outfile;
    gchar *url = URL;
    gdk_threads_enter();
    curl = curl_easy_init();
    if(curl)
    {
        outfile = fopen("test.curl", "w");
        if(outfile)
            g_print("curl\n");

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read_func);
        
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, current_treeview);
        gdk_threads_leave();
        res = curl_easy_perform(curl);

        fclose(outfile);
        /* always cleanup */
        curl_easy_cleanup(curl);
    }
    return NULL;
}


G_MODULE_EXPORT void
cb_add( GtkWidget *button,
         Data      *data )
{    
    Data *current_download = (Data *)data;
    GtkTreeIter iter;
    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(current_download->tree)));
    gtk_list_store_append( store, &iter );

    GtkTreeRowReference* reference = NULL;
    GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
    reference = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
    g_hash_table_insert(TreeRowReferences, current_download, reference);
    gtk_tree_path_free(path);

    if (!g_thread_create(&create_thread, current_download, FALSE, NULL) != 0)
        g_warning("can't create the thread");
} 

int main(int argc, char **argv)
{
    GtkBuilder *builder;
    GtkWidget  *window;
    Data       *data;


    curl_global_init(CURL_GLOBAL_ALL);
    if( ! g_thread_supported() )
        g_thread_init( NULL );

    gdk_threads_init();
    gtk_init(&argc, &argv);

    data = g_slice_new( Data );

    /* Create builder */
    builder = gtk_builder_new();
    gtk_builder_add_from_file( builder, "progress.glade", NULL );

    window    = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
    data->down   = GTK_WIDGET( gtk_builder_get_object( builder, "down" ) );
    data->tree = GTK_WIDGET( gtk_builder_get_object( builder, "treeview" ) );
    TreeRowReferences = g_hash_table_new(NULL, NULL);

    gtk_builder_connect_signals( builder, data );
    g_object_unref( G_OBJECT( builder ) );

    gdk_threads_enter();
    gtk_widget_show( window );
    gdk_threads_leave();

    gtk_main();
    g_slice_free( Data, data );

    return 0;
}

person spy8888    schedule 10.12.2009    source источник


Ответы (1)


Вот мои советы, как заставить ваш код работать:

  1. Если для библиотеки есть файл pkg-config, например gthread или libcurl, используйте его вместо того, чтобы смешивать вызовы pkg-config и переключатели -l. Это не было проблемой, но это может вызвать проблемы позже. Итак, скомпилируйте файл следующим образом:

    gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0 gthread-2.0 libcurl` liststore.c -o liststore
    
  2. Если вы используете потоки, всегда, всегда, всегда, заключайте вызов gtk_main() между gdk_threads_enter() и gdk_threads_leave(). Это тоже не было проблемой, но это определенно будет создавать проблемы позже.

  3. Следующий шаг, устраните все предупреждения, извергающиеся по всей консоли. Ошибку легче обнаружить, когда выдаваемое ею предупреждение не теряется среди 50 других предупреждений. И эти предупреждения действительно означают, что вы делаете что-то не так, поэтому не игнорируйте их.

    а. Первые предупреждения, которые я получил, были жалобы на NaN. NaN означает «не число», и это то, что вы получаете, когда делите на ноль. Единственное деление в вашем коде находится в my_progress_func(), поэтому, вероятно, CURL иногда передает ноль в качестве параметра dltotal. Если вы проверите это, это устранит эти предупреждения:

    int my_progress_func(Data *data,
                 double t, /* dltotal */
                 double d, /* dlnow */
                 double ultotal,
                 double ulnow)
    {
        if(t == 0)
            return 0;
        data->progress = d*100.0/t;
        gdk_threads_enter();
        g_idle_add(set_download_progress, data);
        gdk_threads_leave();
        return 0;
    }
    

    б. Следующей причиной предупреждений является оператор unref в конце thread_create(). То, что вы делаете, это использовать gtk_tree_view_get_model() для получения модели из древовидного представления, но это не дает вам ссылки на модель. Поэтому, когда вы отменяете ссылку, вы фактически уничтожаете модель, в то время как древовидное представление все еще использует ее. Удалите это утверждение, и все предупреждения волшебным образом исчезнут. Вам вообще не нужно отменять ссылку на модель. Древовидное представление имеет единственную ссылку на него, и когда древовидное представление уничтожается, оно автоматически удаляет ссылку на модель. Хотя проблема еще не в этом.

  4. Теперь все, что не так, вы можете быть уверены, что проблема не вызвана каким-то недопустимым указателем или проблемой потока. А ведь оказывается что-то совсем простое:

    gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store) ,
                                         &iter,
                                         g_strdup_printf ("%d",0));
    

    Здесь вы всегда получаете итератор, указывающий на строку номер 0, то есть на первую строку. Вот почему весь процесс загрузки отображается в первой строке. Я бы рекомендовал использовать GPrivate, чтобы иметь структуру данных для каждого потока с собственной долей прогресса, и GtkTreeRowReference для строки, которую вы создаете при запуске потока. Получите CURL, чтобы отправить эту структуру данных обратному вызову. Не используйте GtkTreeIter или GtkTreePath для сохранения строки. Вместо этого получите свой GtkTreeIter следующим образом:

    gtk_tree_model_get_iter(gtk_tree_row_reference_get_model(row_reference), &iter, gtk_tree_row_reference_get_path(row_reference));
    

Удачи.

person ptomato    schedule 10.12.2009
comment
Мне пришлось заполнить CAPTCHA, чтобы опубликовать это. Видимо, мое решение настолько хорошо, что сайт думал, что его может придумать только компьютер... ;-) - person ptomato; 11.12.2009
comment
Привет Птомат! Большое спасибо за вашу помощь, я изменил свой код, как вы предлагаете. Мне очень стыдно, мой код все еще застрял. Хоть я и RTFM(GPrivate), но до сих пор не могу понять как им пользоваться. Я отредактировал свой вопрос, обновил измененный код. Не могли бы вы дать мне еще несколько советов? Благодарю вас ! - person spy8888; 11.12.2009
comment
Что касается gtk_tree_model_get_iter(gtk_tree_row_reference_get_model(row_reference), &iter, gtk_tree_row_reference_get_path(row_reference));: документы GTK странно умалчивают об этом, но gtk_tree_row_reference_get_path(row_reference) возвращает путь, созданный gtk_tree_path_copy, поэтому для него выделена память, которая позже будет освобождена с помощью gtk_tree_path_free. В документах также не говорится, что вам нужно освобождать путь, созданный gtk_tree_path_copy, хотя исходники GTK ясно показывают, что для него выделена память. Возможно, просто предполагается, что вам всегда нужно освобождать путь дерева. - person GTK 1.2.6 fanboy; 14.12.2017