在 GTK 中使用多线程?

Posted

技术标签:

【中文标题】在 GTK 中使用多线程?【英文标题】:Using multiple threads with GTK? 【发布时间】:2020-07-04 03:54:42 【问题描述】:

如何我有 2 个线程,一个初始化 GUI,然后连续运行 gtk_main() 来绘制我的 gui,我的另一个线程完成我所有的程序逻辑和计算,但是当我的程序逻辑线程更新 GtkImage 时,它​​会随机且不一致地停止更新和冻结并且不会再在表单上绘制任何东西了。

我已经阅读并被告知只能从 gtk 线程调用 GTK 函数,但这是不可能的,因为一旦你调用 gtk_main(),它就会卡在那里直到程序退出。

有谁知道我如何让我的程序和 gui 协同工作,并向我展示它如何与我现有的程序一起工作的示例?

我已经为此困扰了好几个小时,需要尽快为一家公司完成此脚本,以便他们可以使用它来帮助他们在制造工厂中自动执行其中一项任务。

例如

static GtkWidget* gtk_image;

void my_thread()


while (1) 

//wait for input

//do program logic

//update gtk_image to new image





void gui_thread() 
//initialize gui

//call gtk_main //it never exits this function until program is closed so you can see how its impossible to make any drawing calls from this thread


我的实际代码如下所示(其中 cam_main 和 main_gtk 是我提到的线程):

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <gphoto2/gphoto2-camera.h>
#include <gphoto2/gphoto2-context.h>
#include <wiringPi.h>
#include <gtk/gtk.h>
#include <pthread.h>

    //program
    static int running = 1;
    //libgphoto2
    static CameraList* list;
    static Camera** cams;
    static GPContext* context;
    static const char *name, *value;
    static int ret, count;
    static int pic = 0;
    static int front = 1;
    static const char* workingDir;
    //GTK
    static GtkWidget *window, *vbox,*hboxDir, *hboxCamLabels, *hboxFrontPics, *hboxBackPics, *hboxStatus, *lblDir, *btnConfigDir, *lblCams, *lblFront, *lblBack, *lblCurrentStatus;
    static GtkWidget *front_cams[3];
    static GtkWidget *back_cams[3];
    static GdkPixbuf *pxbscaled = NULL;
    static GdkPixbuf *pixbuf = NULL;
    static GError* err = NULL;

static void btnConfigDir_ConfigureDirectory(GtkWidget* widget, gpointer data) 
    GtkWidget *dialog;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
gint res;

                dialog = gtk_file_chooser_dialog_new ("Open File", window,
                                      action,
                                      "_Cancel",
                                      GTK_RESPONSE_CANCEL,
                                      "_Open",
                                      GTK_RESPONSE_ACCEPT,
                                      NULL);

res = gtk_dialog_run (GTK_DIALOG (dialog));
if (res == GTK_RESPONSE_ACCEPT)
  
    char *filename;
    GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
    workingDir = gtk_file_chooser_get_filename (chooser);
  

  //gtk_label_set_text((GTK_LABEL)lblDir, workingDir);

gtk_widget_destroy (dialog);


void* main_gtk() 
    //GTK WIDGET QUEUE REDRAW WOULD REDRAW AT APPROPRIATE TIME


    //INIT GTK
    gtk_init(NULL, NULL);

    //SETUP WINDOW
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    //button1=gtk_button_new_with_label("Click me");
    //gtk_button_set_label(GTK_BUTTON(button1), "click me 1");
    g_signal_connect(window,"delete-event", G_CALLBACK(gtk_main_quit), NULL);

    workingDir = "/media/pi/SD CARD";
    lblDir = gtk_label_new("Save to: dir\t");
    btnConfigDir = gtk_button_new_with_label("Configure Directory");
    g_signal_connect(btnConfigDir, "clicked", G_CALLBACK(btnConfigDir_ConfigureDirectory), NULL);
    lblCams = gtk_label_new("\t\t\t\t\t\t\t\tCAM 1\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCAM2\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCAM3");
    lblFront = gtk_label_new("Front");
    lblBack = gtk_label_new("Back ");
    lblCurrentStatus = gtk_label_new("Current Status: Idle");

    //SET WINDOW SIZE AND TITLE
    //gtk_widget_set_size_request(window, 600, 400);
    gtk_window_set_title(GTK_WINDOW(window), "CaptureGui");

    //RESIZE IMAGES
    //image
    front_cams[0] = gtk_image_new();
    front_cams[1] = gtk_image_new();
    front_cams[2] = gtk_image_new();
    back_cams[0] = gtk_image_new();
    back_cams[1] = gtk_image_new();
    back_cams[2] = gtk_image_new();

    GdkPixbuf* pxb;
    pxb = gdk_pixbuf_new(0, 0, 8, 500, 300);
    gtk_image_set_from_pixbuf(GTK_IMAGE(front_cams[0]), pxb);
    gtk_image_set_from_pixbuf(GTK_IMAGE(front_cams[1]), pxb);
    gtk_image_set_from_pixbuf(GTK_IMAGE(front_cams[2]), pxb);
    gtk_image_set_from_pixbuf(GTK_IMAGE(back_cams[0]), pxb);
    gtk_image_set_from_pixbuf(GTK_IMAGE(back_cams[1]), pxb);
    gtk_image_set_from_pixbuf(GTK_IMAGE(back_cams[2]), pxb);

    //g_print("1");

    //front_cams[0] = gtk_image_new_from_file("/media/pi/SD CARD/cam-1_0_a_front.jpg");
    //set_image_scaled(front_cams[0], "/media/pi/SD CARD/cam-1_1_a_front.jpg");
    //set_image_scaled(front_cams[1], "/media/pi/SD CARD/cam-1_1_a_front.jpg");
    //set_image_scaled(front_cams[2], "/media/pi/SD CARD/cam-1_1_b_back.jpg");

    //PACK
    hboxDir = gtk_box_new(0, 0);
    hboxCamLabels = gtk_box_new(0, 0);
    hboxFrontPics = gtk_box_new(0, 0);
    hboxBackPics = gtk_box_new(0, 0);
    hboxStatus = gtk_box_new(0, 0);
    vbox = gtk_vbox_new(0, 10);
    gtk_box_pack_end(GTK_BOX(hboxDir), btnConfigDir, 0, 0, 0);
    gtk_box_pack_end(GTK_BOX(hboxDir), lblDir, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(hboxCamLabels), lblCams, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(hboxFrontPics), lblFront, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(hboxFrontPics), front_cams[0], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxFrontPics), front_cams[1], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxFrontPics), front_cams[2], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxBackPics), lblBack, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(hboxBackPics), back_cams[0], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxBackPics), back_cams[1], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxBackPics), back_cams[2], 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(hboxStatus), lblCurrentStatus, 0, 0, 10);
    gtk_box_pack_start(GTK_BOX(vbox), hboxDir, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hboxCamLabels, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hboxFrontPics, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hboxBackPics, 0, 0, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hboxStatus, 0, 0, 0);


    //gtk_box_pack_start(GTK_BOX(hbox), front_cams[0], 0, 0, 0);
    //gtk_box_pack_start(GTK_BOX(hbox), front_cams[1], 0, 0, 0);
    //gtk_box_pack_start(GTK_BOX(hbox), front_cams[2], 0, 0, 0);
    //gtk_box_pack_start(GTK_BOX(hbox), back_cams[0], 0, 0, 0);
    //gtk_box_pack_start(GTK_BOX(hbox), back_cams[1], 0, 0, 0);
    //gtk_box_pack_start(GTK_BOX(hbox), back_cams[2], 0, 0, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    //ADD ELEMENTS TO GUI
    //gtk_container_add(GTK_CONTAINER(window), image1);
    //gtk_container_add(GTK_CONTAINER(window), image2);
    //image2 = gtk_image_new_from_file("/media/pi/SD CARD/cam-1_0_b_back.jpg");
    //gtk_container_add(GTK_CONTAINER(window), image2);

    //SHOW GUI
    gtk_widget_show_all(window);
    gtk_main();


void set_image_scaled(GtkWidget* img, const char* path) 
    pixbuf = gdk_pixbuf_new_from_file(path, &err);
    g_assert_no_error(err);
    pxbscaled = gdk_pixbuf_scale_simple(pixbuf, 500, 300, GDK_INTERP_BILINEAR);
    gtk_image_set_from_pixbuf(GTK_IMAGE(img), pxbscaled);
    g_object_unref(pixbuf);
    g_object_unref(pxbscaled);


void* cam_main() 
    while (running == 1) 
    if (digitalRead(4) == 0)
        
            printf("taking pics of %s item %i\n", (front == 1) ? "front" : "back", pic);
            for (int i = 0; i < count; i++)
            
                int fd, retval;
                CameraFile *file;
                CameraFilePath cfPath;
                strcpy(cfPath.folder, "/");
                strcpy(cfPath.name, "foo1.jpg");
                printf("Capturing cam%i...\n", i + 1);
                int res = gp_camera_capture(cams[i], GP_CAPTURE_IMAGE, &cfPath, context);
                //printf(gp_port_result_as_string(res));
                printf("capture result: %i\n", res);
                //Camera won't take pic if busy and will continue to program end
                char buf[256];
                snprintf(buf, sizeof(buf), "%s/cam-%i_%i_%s.jpg", workingDir, i + 1, pic, (front == 1) ? "a_front" : "b_back"); //a_ to make front come before back otherwise systems will order incorrectly
                fd = open(buf, O_CREAT | O_WRONLY, 0644);
                retval = gp_file_new_from_fd(&file, fd);
                retval = gp_camera_file_get(cams[i], cfPath.folder, cfPath.name, GP_FILE_TYPE_NORMAL, file, context);
                retval = gp_camera_file_delete(cams[i], cfPath.folder, cfPath.name, context);
                gp_file_free(file);
                //debug
                //if (front == 1 && i == 0)
                //  set_image_scaled(front_cams[0], buf);
                if (front == 1)
                    set_image_scaled(front_cams[i], buf);
                else
                    set_image_scaled(back_cams[i], buf);
            
            if (front == 1)
                front = 0;
            else
            
                front = 1;
                pic += 1;
            
            printf("pics taken...\n");
        
    


int main(int argc, char **argv)

    //Kill any processes using cams
    system("pkill -f gphoto2");

    //Wiring pi init
    wiringPiSetupGpio();

    //Init
    context = gp_context_new();

    detect_cams();


    pthread_t logic_thread_handle, gui_thread_handle;
    pthread_create(&logic_thread_handle, NULL, cam_main, NULL);
    pthread_create(&gui_thread_handle, NULL, main_gtk, NULL);
    pthread_join(gui_thread_handle, 0);
    pthread_join(logic_thread_handle, 0);

    //Deinit
    for (int i = 0; i < count; i++)
    
        gp_camera_exit(cams[i], context);
        gp_camera_free(cams[i]);
    
    return 0;


void detect_cams() 
    //Detecting all cameras and loading them into mem
        //Detecting all cameras
    ret = gp_list_new(&list);
    if (ret < GP_OK) return 1;
    gp_list_reset(list);
    count = gp_camera_autodetect(list, context);
    if (count < 1)
    
        printf("No cameras detected.\n");
        return 1;
    


    //Open all cameras
    printf("Number of cameras: %d\n", count);
    cams = calloc(sizeof (Camera*), count);
    for (int i = 0; i < count; i++)
    
        gp_list_get_name(list, i, &name);
        gp_list_get_value(list, i, &value);
        ret = open_cam(&cams[i], name, value, context);
        if (ret < GP_OK)
            fprintf(stderr, "Camera %s on port %s failed to open\n", name, value);
    


int open_cam(Camera ** camera, const char *model, const char *port, GPContext *context) 
    GPPortInfoList      *portinfolist = NULL;
    CameraAbilitiesList *abilities = NULL;
    int     ret, m, p;
    CameraAbilities a;
    GPPortInfo  pi;

    ret = gp_camera_new (camera);
    if (ret < GP_OK) return ret;

    if (!abilities) 
        /* Load all the camera drivers we have... */
        ret = gp_abilities_list_new (&abilities);
        if (ret < GP_OK) return ret;
        ret = gp_abilities_list_load (abilities, context);
        if (ret < GP_OK) return ret;
    

    /* First lookup the model / driver */
        m = gp_abilities_list_lookup_model (abilities, model);
    if (m < GP_OK) return ret;
        ret = gp_abilities_list_get_abilities (abilities, m, &a);
    if (ret < GP_OK) return ret;
        ret = gp_camera_set_abilities (*camera, a);
    if (ret < GP_OK) return ret;

    if (!portinfolist) 
        /* Load all the port drivers we have... */
        ret = gp_port_info_list_new (&portinfolist);
        if (ret < GP_OK) return ret;
        ret = gp_port_info_list_load (portinfolist);
        if (ret < 0) return ret;
        ret = gp_port_info_list_count (portinfolist);
        if (ret < 0) return ret;
    

    /* Then associate the camera with the specified port */
        p = gp_port_info_list_lookup_path (portinfolist, port);
        switch (p) 
        case GP_ERROR_UNKNOWN_PORT:
                fprintf (stderr, "The port you specified "
                        "('%s') can not be found. Please "
                        "specify one of the ports found by "
                        "'gphoto2 --list-ports' and make "
                        "sure the spelling is correct "
                        "(i.e. with prefix 'serial:' or 'usb:').",
                                port);
                break;
        default:
                break;
        
        if (p < GP_OK) return p;

        ret = gp_port_info_list_get_info (portinfolist, p, &pi);
        if (ret < GP_OK) return ret;
        ret = gp_camera_set_port_info (*camera, pi);
        if (ret < GP_OK) return ret;
    return GP_OK;

【问题讨论】:

您使用的是 GTK3 吗?那么***.com/questions/30607429/… 可能会对您有所帮助。 【参考方案1】:

我已经阅读并被告知只能从 gtk 线程调用 GTK 函数,但这是不可能的,因为一旦你调用 gtk_main(),它就会卡在那里直到程序退出。

不,这并非不可能。即使gtk_main() 处于活动状态,控制权也会通过回调返回给您的程序。在您的线程中,您可以使用gdk_threads_add_idle() 安排在 GTK 线程中调用的回调。请参阅GTK3 and multithreading, replacing deprecated functions 了解更多信息。

【讨论】:

以上是关于在 GTK 中使用多线程?的主要内容,如果未能解决你的问题,请参考以下文章

gtk+多线程的程序实例

GTK+ Toggle动作

哪些 GUI 框架最适合多线程 Python 程序?

GTK中线程的死锁问题

多线程在项目中经常使用的5种场景

GTK+ 接口应该在单独的线程中运行吗?