如何从其他线程访问QWidget
How to access QWidget from other threads
我有
struct MyWidget : QWidget {
// non-GUI related stuff:
int data;
int doSth();
};
我需要从另一个线程(即不是主线程)访问MyWidget
实例。有什么安全的方法吗?我知道我无法访问与GUI相关的功能,因为有些后端(例如MacOSX/Cocoa)不支持。但是,在本例中,我只需要访问data
或doSth()
。但据我所知,根本无法保证对象的生存期——也就是说,如果包含该小部件的父窗口关闭,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导入互斥体保护(Python不允许并行import
s)在其他一些Python线程中,会调用其他一些
import
。import
现在锁定了Python导入互斥体。当它在做自己的事情时,它会在某个时候进行一些GC清理。GC清理调用某个对象的遍历函数,该对象包含MyWidget
。因此,它必须访问MyWidget
。但是,execInMainThread_sync
(或等效的工作解决方案)将死锁,因为主线程当前正在等待Python导入锁。
注意:Python全局解释器锁并不是真正的问题。当然,它会在任何execInMainThread_sync
调用之前解锁。然而,我无法真正检查任何其他潜在的Python/whatever锁。特别是,我不允许只解锁Python导入锁——这是有原因的。
你可能会想到的一个解决方案是真正避免在主线程中使用任何Python代码。但这有很多缺点,例如它会很慢、复杂和丑陋(GUI基本上只显示来自Python的数据,因此需要有一个巨大的代理/包装器)。我认为在某些时候我仍然需要等待Python数据,所以我只在其他时候介绍可能的死锁情况。
此外,如果我可以从另一个线程安全地访问MyWidget
,那么所有的问题都会消失。与上面的解决方案相比,引入全局互斥是更干净、更短的解决方案。
您可以使用信号/插槽机制,但如果GUI控件的数量很大,它可能会很乏味。我建议使用单个信号和插槽来控制gui。通过struct
发送更新GUI所需的所有信息。
void SomeWidget::updateGUISlot(struct Info const& info)
{
firstControl->setText(info.text);
secondControl->setValue(info.value);
}
如果收件人被删除,您不需要担心发出信号。此细节由Qt
处理。或者,您可以在退出GUI线程事件循环后等待线程退出。您需要使用Qt
注册该结构。
编辑:
根据我从你的扩展问题中读到的内容,你的问题与线程之间的通信有关。尝试管道、(POSIX)消息队列、套接字或POSIX信号,而不是用于线程间通信的Qt
信号。
就我个人而言,我不喜欢GUI内容(即:小部件)具有非GUI相关内容的设计。。。我认为你应该把这两者分开。Qt需要将GUI对象始终保持在主线程上,但任何其他对象(QObject派生的)都可以移动到线程(QObject::moveToThread)。
您所解释的似乎与小部件、Qt或类似的东西完全无关。这是Python及其线程和锁结构固有的问题,如果你是多线程的,这是没有意义的。Python基本上假设任何对象都可以从任何线程访问。使用任何其他工具包都会遇到同样的问题。可能有一种方法可以告诉Python不要这样做——我对cpython实现的细节了解不够,但这正是你需要了解的地方。
GC清理调用某个对象的遍历函数,该对象包含MyWidget
这是你的问题。您必须确保这种跨线程GC清理不会发生。我不知道你会怎么做:(
我担心的是,尽管每个人都声称只有C/C++才能让你在如此大规模的规模上做到这一点,但你使用Python却悄悄地、巧妙地给自己开了一枪。
我的解决方案:
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
。
- 通过方法访问结构
- 使用不带参数的函数访问结构元素
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- 用于访问容器<T>数据成员的正确 API
- 访问者访问变体并返回不同类型时出错
- 尝试通过多个向量访问变量时,向量下标超出范围
- 无法访问嵌套类.类的使用无效
- 写入位置0x0000000C时发生访问冲突
- 我们可以访问一个不存在的联盟的成员吗
- C++从另一个类访问公共静态向量的正确方法是什么
- 我的简单if-else语句是如何无法访问的代码
- 从C++dll访问C#中的一行主要参数
- 概念TS检查忽略私有访问修饰符
- 访问被拒绝后,c++中的故障保护代码
- 在c++中访问int到类对象的映射时出错
- 从 QWidgetList 访问 QWidget 子插槽/信号 (QList<QWidget*>)
- 如何访问指向 QWidget 像素的指针
- 如何从其他线程访问QWidget
- 为什么QWidget是通过指针访问的
- 使QHBoxLayout在QWidget的实例中可访问