Безопасно излучать сигнал из потока и подключить его к виджету

Я использую Gtkmm и многопоточность.

У меня есть класс "NetworkWorker" Doig вещи с сетью во вторичном потоке. В этом классе я хочу сделать много сигналов, которые будут обрабатываться моим классом "MainWindow".

Методы, которые обрабатывают эти сигналы, будут редактировать добавляемый текст в TextView.

У меня есть следующий код:

NetworkWorker.h

#ifndef         NETWORKWORKER_H_
# define        NETWORKWORKER_H_

# include       <sigc++/sigc++.h>
# include       <glibmm/threads.h>
# include       <string>

class NetworkWorker
{
 public:
  NetworkWorker();
  ~NetworkWorker();

  void start();
  void stop();

  sigc::signal<void, std::string&>& signal_data_received();

 private:
  void run();

  sigc::signal<void, std::string&> m_signal_data_received;

  Glib::Threads::Thread* m_thread;
  Glib::Threads::Mutex m_mutex;
  bool m_stop;
};

#endif

NetworkWorker.c

#include        <cstdlib>
#include        <glibmm/timer.h>
#include        <glibmm/threads.h>
#include        <iostream>
#include        <sigc++/sigc++.h>

#include        "NetworkWorker.h"

NetworkWorker::NetworkWorker() :
  m_thread(NULL), m_stop(false)
{

}

NetworkWorker::~NetworkWorker()
{
  stop();
}

void NetworkWorker::start()
{
  if (!m_thread)
    m_thread = Glib::Threads::Thread::create(sigc::mem_fun(*this, &NetworkWorker::run));
}

void NetworkWorker::stop()
{
  {
    Glib::Threads::Mutex::Lock lock(m_mutex);
    m_stop = true;
  }

  if (m_thread)
    m_thread->join();
}

sigc::signal<void, std::string&>& NetworkWorker::signal_data_received()
{
  return m_signal_data_received;
}

void NetworkWorker::run()
{
  while (true)
    {
      {
        Glib::Threads::Mutex::Lock lock(m_mutex);
        if (m_stop)
          break;
      }
      Glib::usleep(5000);
      std::cout << "Thread" << std::endl;
      std::string* str = new std::string("MyData");
      m_signal_data_received.emit(*str);
    }
}

mainwindow.h

#ifndef         MAIN_WINDOW_H_
# define        MAIN_WINDOW_H_

# include       <gtkmm/textview.h>
# include       <gtkmm/window.h>
# include       <string>

class MainWindow : public Gtk::Window
{
 public:
  MainWindow();
  ~MainWindow();

  void appendText(const std::string& str);

 private:
  Gtk::TextView m_text_view;
};

#endif

MainWindow.c

#include        <gtkmm/notebook.h>
#include        <gtkmm/widget.h>
#include        <iostream>
#include        <string>

#include        "MainWindow.h"

MainWindow::MainWindow()
{
  set_title("My App");
  set_default_size(800, 600);
  add(m_text_view);
}

MainWindow::~MainWindow()
{

}

void MainWindow::appendText(const std::string& str)
{
  std::string final_text = str + "\n";
  Glib::RefPtr<Gtk::TextBuffer> buffer = m_text_view.get_buffer();
  Gtk::TextBuffer::iterator it = buffer->end();
  buffer->insert(it, final_text);
  Glib::RefPtr<Gtk::Adjustment> adj = m_text_view.get_vadjustment();
  adj->set_value(adj->get_upper() - adj->get_page_size());
}

и мой main.cpp

#include        <cstdlib>
#include        <gtkmm/main.h>
#include        <iostream>
#include        <string>

#include        "MainWindow.h"
#include        "NetworkWorker.h"

void            recv(const std::string& str)
{
  std::cout << str << std::endl;
}

int             main(int argc, char **argv)
{
  Gtk::Main     app(Gtk::Main(argc, argv));
  MainWindow    main_window;
  NetworkWorker network_worker;

  main_window.show_all();

  network_worker.signal_data_received().connect(sigc::ptr_fun(&recv));
  network_worker.signal_data_received().connect(sigc::mem_fun(main_window, &MainWindow::appendText));
  network_worker.start();

  Gtk::Main::run(main_window);
  return (EXIT_SUCCESS);
}

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

Когда я выполняю этот код, я получаю следующий вывод:

$> ./client 
Thread
MyData
Thread
MyData
[...]
Thread
MyData
Thread
MyData
(client:5596): Gtk-CRITICAL **: gtk_text_layout_real_invalidate: assertion 'layout->wrap_loop_count == 0' failed
Thread
MyData
Thread
MyData
[...]
Thread
MyData
Thread
MyData
[1]    5596 segmentation fault (core dumped)  ./client

Может ли кто-нибудь помочь мне решить эту проблему?:)

1 ответ

Решение

Проблема заключается в том, что вы вызываете не поточный безопасный вызов функции (обратные вызовы сигналов не являются поточными).

Так что вам нужно использовать что-то вроде Glib::signal_idle().connect( sigc::mem_fun(*this, &IdleExample::on_idle) );(или что-то эквивалентное вызову C API g_idle_add(GCallback func)) из вашей темы. Эта функция является поточно-ориентированной (по крайней мере, из API C).

Смотрите этот учебник для упрощенного примера.


Никогда не вызывайте и не передавайте сигналы из разных потоков при использовании библиотек пользовательского интерфейса. Обычно API предназначены для вызова из одного потока. Это самая частая ошибка при использовании инструментария пользовательского интерфейса.

Другие вопросы по тегам