3

我正在为一些计算密集型任务(机器视觉)开发一个 C++ 类库。

// I am a part of a Qt-agnostic library
class Cruncher
{
    /* ... */
public:
    void doStuff();
};

然后有一个使用该库的 Qt GUI。我正在创建一个工作线程来调用库中的繁重例程:

// I am a part of a Qt-based GUI which utilizes the library
class Worker : public QThread
{
    /* ... */
protected:
    virtual void run()
    {
        /* ... */
        Cruncher c;
        for (int i = 0; i < count; ++i)
            c.doStuff(); // takes some time, and while it's working 
                         // it should communicate status changes which should
                         // become visible in the GUI
    }
};

现在在 doStuff() 内部发生了很多事情,我想向用户提供一些关于正在发生的事情的反馈,而无需等待 doStuff() 返回。一方面,也许比在每次调用 doStuff() 后将仪表增加一步更精细的进度报告。此外,doStuff() 可能会遇到非关键故障,使其继续部分工作,但我希望在 Cruncher 工作时出现这种情况时在 GUI 中显示一条消息(并且 Worker 目前正忙于调用做东西())。

我希望库保持独立于 Qt,所以我不愿意向 Cruncher 添加信号和插槽。当它不是 Qt 类时,还有什么其他方法可以让它向 GUI 提供反馈以报告它的工作?

我正在考虑创建一个 QTimer,它会在 Worker 运行时以固定的时间间隔轮询 Cruncher 的一些“状态”和“errorMsg”成员,但这似乎非常不理想。

4

3 回答 3

4

我发布了我自己的答案,因为虽然我接受了@Nim 的建议,但我希望答案更冗长一些,因此如果有人遇到同样的问题会更有用。

我在库中创建了消息调度程序的骨架:

// doesn't need to know about Qt
class MessagePort
{
public:
    virtual void message(std::string msg) = 0;
};

接下来,我向 Cruncher 添加了该对象的句柄,并通过偶尔调用 message() 来添加 doStuff():

// now with Super Cow powers!
class Cruncher
{
protected:
    MessagePort *msgPort_;

public:
    Cruncher(MessagePort *msgPort) : msgPort_(msgPort) {}
    void doStuff()
    {
        while(...)
        {
            /*...*/
            msgPort_->message("Foo caused an overload in Bar!");
        }
    }
};

最后,我使用所有必要的 Qt 优点在 GUI 中制作了 MessagePort 的实现:

class CruncherMsgCallback : public QObject, public MessagePort
{
    Q_OBJECT

public:
    CruncherMsgCallback() : QObject(), MessagePort()
    {
        connect(this, SIGNAL(messageSignal(const QString &)), 
                GUI,    SLOT(messageShow(const QString &)), 
                Qt::QueuedConnection);
    }

    virtual void message(std::string msg)
    {
        emit messageSignal(QString::fromStdString(msg));
    }

signals:
    void messageSignal(const QString &msg);
};

最后,当 Worker 创建 Cruncher 的一个实例时,它还会给它一个指向正在工作的 MessagePort 的指针:

class Worker
{
protected:
    virtual void run()
    {
        CruncherMsgCallback msgC;
        Cruncher c(&msgC); // &msgC works as a pointer to a 
                           // generic MessagePort by upcasting
        c.doStuff(); // Cruncher can send messages to the GUI 
                     // from inside doStuff()
    }
};
于 2013-02-07T19:13:34.430 回答
1

使用回调函数(类)等,并在构造过程中将其传入。您需要报告的事情,通过该回调报告。

于 2013-02-07T15:11:37.783 回答
1

您可以安全地从该方法发出信号run(),我认为这是将信息从工作线程传递到主线程的最佳方式。只需将信号添加到您的 QThread 子类(如果您完全不确定 QThread 线程如何工作,请避免添加插槽)。

最好使来自这些信号的连接显式排队,以避免出现问题。虽然默认的自动连接类型也应该可以工作并发出排队信号,但我认为在这种情况下最好是明确的。实际上,直接信号也应该这样工作,但是您必须自己处理线程安全,而不是让 Qt 为您处理它,并且您无法连接到使用任何仅在 main 中工作的 QtGui 类的插槽线程,所以最好坚持排队连接。

要将简单的信息传递给run()方法,并且如果不需要立即响应,则可以使用一些共享QAtomicInt变量或类似的东西作为标志,工作线程在方便时会检查它们。稍微复杂一点的方法,仍然需要轮询,是拥有你用互斥锁保护的共享数据结构。与该方向通信的更复杂的方式将涉及某种消息队列(就像 Qt 在主线程的事件循环中使用的那样,当您向该方向发出信号时)。

于 2013-02-07T15:22:19.667 回答