如何从其他线程访问QWidget

时间:2014-02-13 10:04:05

标签: c++ multithreading qt qwidget

我有

struct MyWidget : QWidget {
    // non-GUI related stuff:
    int data;
    int doSth();
};

我需要从另一个线程(即不是主线程)访问MyWidget实例。有没有办法安全地做到这一点?我知道我无法访问GUI相关的功能,因为一些后端(例如MacOSX / Cocoa)不支持它。但是,我只需要在此示例中访问datadoSth()。但根据我的理解,根本无法保证对象的生命周期 - 即如果具有该窗口小部件的父窗口关闭,MyWidget实例将被删除。

还是有办法保证一生吗?我猜QSharedPointer不起作用,因为QWidget在内部执行其生命周期处理,具体取决于父窗口小部件。 QPointer当然也无济于事,因为它只是微弱而且没有锁定机制。

我目前的解决方法基本上是:

int widget_doSth(QPointer<MyWidget> w) {
    int ret = -1;
    execInMainThread_sync([&]() {
        if(w)
            ret = w->doSth();
    });
    return ret;
}

execInMainThread_sync使用QMetaMethod::invoke来调用主线程中的方法。)

但是,由于某些特定原因,该解决方法不再起作用(我稍后会解释原因,但这并不重要)。基本上,我无法在主线程中执行某些操作(出于一些复杂的死锁原因)。

我目前正在考虑的另一个解决方法是添加一个全局互斥锁来保护MyWidget析构函数,而在析构函数中,我正在清理对MyWidget的其他弱引用。然后,在其他地方,当我需要确保生命周期时,我只需锁定该互斥锁。


我当前的解决方法不再起作用的原因(这仍然是真实情况的简化版本):

  • MyWidget中,data实际上是PyObject*

  • 在主线程中,会调用一些Python代码。 (在我的应用程序的主线程中根本不可能避免任何Python代码调用。)Python代码最终会执行一些import,这是由一些Python-import-mutex保护的(Python不支持允许并行import s。)

  • 在其他一些Python线程中,调用了其他一些importimport现在锁定了Python-import-mutex。虽然它正在做它的事情,它在某些时候做了一些GC清理。 GC清理调用某个包含MyWidget的对象的遍历函数。因此,它必须访问MyWidget。但是,execInMainThread_sync(或等效的工作解决方案)将会死锁,因为主线程当前正在等待Python-import-lock。

注意:Python全局解释器锁并不是真正的问题。当然,它会在任何execInMainThread_sync电话之前解锁。但是,我无法真正检查任何其他潜在的Python /无论锁定。 ESP。我不被允许解锁Python-import-lock - 这是有原因的。

您可能会想到的一个解决方案是在主线程中完全避免使用任何Python代码。但这有很多缺点,例如:它将是缓慢,复杂和丑陋的(GUI基本上只显示来自Python的数据,因此需要一个巨大的代理/包装它)。我认为我仍然需要在某些时候等待Python数据,所以我只是在其他方面介绍可能的死锁情况。

此外,如果我可以安全地从另一个线程访问MyWidget,所有问题都会消失。与上述相比,引入全局互斥体是一种更清洁,更短的解决方案。

4 个答案:

答案 0 :(得分:2)

您可以使用信号/插槽机制,但如果GUI控件的数量很大,则可能会很繁琐。我建议使用单个信号和插槽来控制gui。通过struct发送更新GUI所需的所有信息。

void SomeWidget::updateGUISlot(struct Info const& info)
{
  firstControl->setText(info.text);
  secondControl->setValue(info.value);
}

如果删除了收件人,则无需担心发出信号。此详细信息由Qt处理。或者,您可以在退出GUI线程事件循环后等待线程退出。您需要使用Qt register结构{{3}}。

编辑:

从我从扩展问题中读到的内容来看,问题与线程之间的通信有关。尝试使用管道,(POSIX)消息队列,套接字或POSIX信号而不是Qt信号进行线程间通信。

答案 1 :(得分:0)

就我个人而言,我不喜欢GUI内容(即:小部件)具有非GUI相关内容的设计...我认为你应该将这两者相互分开。 Qt需要始终将GUI对象保留在主线程上,但是其他任何东西(QObject派生的)都可以移动到线程(QObject :: moveToThread)。

答案 2 :(得分:0)

似乎您所解释的内容与小部件,Qt或其他任何内容完全无关。这是Python及其线程和锁定结构固有的问题,如果你是多线程则没有意义。 Python基本上假设任何对象都可以从任何线程访问。使用任何其他工具包时都会遇到同样的问题。可能有一种方法告诉Python不要这样做 - 我对cpython实现的细节知之甚少,但那是你需要查看的地方。

  

GC清理调用某个包含MyWidget

的对象的遍历函数

那是你的问题。您必须确保不会发生此类跨线程GC清理。我不知道你会怎么做:(

我担心的是,尽管每个人都声称只有C / C ++允许你这么大规模地做到这一点,但是你已经悄悄地用巧妙的方式用自己的方式射击自己了。

答案 3 :(得分:0)

我的解决方案:

struct MyWidget : QWidget {
    // some non-GUI related stuff:
    int someData;
    virtual void doSth();   

    // We reset that in the destructor. When you hold its mutex-lock,
    // the ref is either NULL or a valid pointer to this MyWidget.
    struct LockedRef {
        boost::mutex mutex;
        MyWidget* ptr;      
        LockedRef(MyWidget& w) : ptr(&w) {}
        void reset() {
            boost::mutex::scoped_lock lock(mutex);
            ptr = NULL;
        }
    };  
    boost::shared_ptr<LockedRef> selfRef;

    struct WeakRef;

    struct ScopedRef {
        boost::shared_ptr<LockedRef> _ref;
        MyWidget* ptr;
        bool lock;
        ScopedRef(WeakRef& ref);
        ~ScopedRef();
        operator bool() { return ptr; }
        MyWidget* operator->() { return ptr; }
    };

    struct WeakRef {
        typedef boost::weak_ptr<LockedRef> Ref;
        Ref ref;
        WeakRef() {}
        WeakRef(MyWidget& w) { ref = w.selfRef; }
        ScopedRef scoped() { return ScopedRef(*this); }
    };

    MyWidget();
    ~MyWidget();
};



MyWidget::ScopedRef::ScopedRef(WeakRef& ref) : ptr(NULL), lock(true) {
   _ref = ref.ref.lock();
   if(_ref) {
       lock = (QThread::currentThread() == qApp->thread());
       if(lock) _ref->mutex.lock();
       ptr = _ref->ptr;
   }
}

MyWidget::ScopedRef::~ScopedRef() {
   if(_ref && lock)
       _ref->mutex.unlock();
}

MyWidget::~QtBaseWidget() {
    selfRef->reset();
    selfRef.reset();
}

MyWidget::MyWidget() {
    selfRef = boost::shared_ptr<LockedRef>(new LockedRef(*this));
}

现在,我需要传递一个MyWidget指针,我正在使用:

MyWidget::WeakRef widget;

我可以从另一个这样的线程中使用它:

MyWidget::ScopedRef widgetRef(widget);
if(widgetRef)
    widgetRef->doSth();

这是安全的。只要存在ScopedRef,就无法删除MyWidget。它将在其析构函数中阻塞。或者它已被删除并ScopedRef::ptr == NULL