QThreads , QObject and sleep function

前端 未结 4 1081
醉梦人生
醉梦人生 2020-12-16 04:18

The problem I encountered is that I decided to implement QThreads the way they are supposed to, based on numerous articles:
http://blog.qt.digia.com/blog/20

相关标签:
4条回答
  • 2020-12-16 04:36

    The simple answer: you're not supposed to block in asynchronous, run-to-completion code -- every event handler and slot implementation in a QObject is supposed to do its job and return, as soon as possible. It's not supposed to do any sort of busy waiting or sleeping. For more ranting along this line, see Miro Samek's I hate RTOSes.

    For a much better implementation that follows from the above, see this answer instead. Macro trickery that follows below is best left to the poor souls stuck with C.

    I've attached an example of how to do it the right way at least from the point of view of what the code does. If you want a real implementation, look no farther than Boost's stackless coroutines.

    The macro trickery is syntactic sugar - it makes the technique more palatable (Boost does it better than I do below). Whether you use macros or write out the methods explicitly, is up to you. The syntax is not what is claimed to be the "right way" of doing it. I'm not the only one to use such preprocessor trickery. Missing is support nested function calls, and multiple "threads" of run-to-completion execution within a QObject. The example shows code for only one "thread" and only one level of async function calls. Stackless Python takes this to the logical conclusion.

    You'll see this pattern in all of your code if you write it in an asynchronous way. The SLEEP macro is syntax sugar to help make the code easier to follow. There's no truly clean way to write it without a hacky macro in C++ where the syntax wouldn't be overbearing. Even as of C++11, the language has no built-in support for yield. See Why wasn't yield added to C++0x?.

    This is truly non-blocking code, you'll see that the periodic timer event fires while you're "asleep". Do note that this cooperative multitasking has a much lower overhead than thread/process switches done by the OS. There's a reason why 16 bit Windows application code was written this way: it performs quite well, even on meager hardware.

    Note that this code does not need a QThread, and in fact doesn't use a QThread, although if you'd move the object to a high priority thread, the delays will have lower spread.

    The Qt timer implementation is clever enough to decrease the timer tick period on Windows, if the period is "short". You can use the platform-specific code I show below, but it should be discouraged. On Qt 5, you'd simply start a Qt::PreciseTimer timer. Do note that on pre-Windows 8 systems you're trading off power consumption and a slightly higher kernel overhead for performance here. Windows 8, OS X (xnu) and modern Linux are tickless and don't suffer from such performance degradation.

    I should acknowledge the clear preprocessor abuse direction from Creating C macro with ## and __LINE__ (token concatenation with positioning macro).

    Similarly to the SLEEP() macro, you can also implement a GOTO() macro, to allow you having simple finite state machines that are written in an easier-to-follow blocking code style, yet are asynchronous behind the scenes. You can have ENTER() and LEAVE() macros to implement actions to be done on state entry and exit, etc, yet the code can look entirely like a straight-coded blocking-style function. I've found it quite productive, and easier to follow than code that lacks any syntactic sugarcoating. YMMV. In the end, you would have something that's on the way to UML statecharts, but with less overhead (both runtime and code-text-wise) than QStateMachine-based implementations.

    Below is the output, the asterisks are periodic timer ticks.

    doing something
    *
    *
    *
    *
    *
    *
    *
    *
    *
    *
    slept, a=10
    *
    *
    *
    *
    *
    slept, a=20
    *
    *
    slept, a=30
    *
    slept, a=40
    
    #sleep.pro
    QT       += core
    QT       -= gui
    TARGET = sleep
    CONFIG   += console
    CONFIG   -= app_bundle
    TEMPLATE = app
    SOURCES += main.cpp
    
    //main.cpp
    #ifdef Q_WS_WIN
    #include <windows.h>
    #endif
    #include <cstdio>
    #include <QtCore/QTextStream>
    #include <QtCore/QObject>
    #include <QtCore/QBasicTimer>
    #include <QtCore/QTimer>
    #include <QtCore/QCoreApplication>
    
    QTextStream out(stdout);
    
    // this order is important
    #define TOKENPASTE2(x,y) x ## y
    #define TOKENPASTE(x,y) TOKENPASTE2(x,y)
    #define SLEEP(ms) sleep(ms, &SLEEPCLASS::TOKENPASTE(fun, __LINE__)); } void TOKENPASTE(fun, __LINE__)() {
    
    class Object : public QObject
    {
        Q_OBJECT
        #define SLEEPCLASS Object // used by the SLEEP macro
    public:
        Object() {
            QTimer::singleShot(0, this, SLOT(slot1()));
            periodic.start(100);
            connect(&periodic, SIGNAL(timeout()), SLOT(tick()));
        }
    protected slots:
        void slot1() {
            a = 10; // use member variables, not locals
            out << "doing something" << endl;
            sleep(1000, &Object::fun1);
        }
        void tick() {
            out << "*" << endl;
        }
    
    protected:
        void fun1() {
            out << "slept, a=" << a << endl;
            a = 20;
            SLEEP(500);
            out << "slept, a=" << a << endl;
            a = 30;
            SLEEP(250);
            out << "slept, a=" << a << endl;
            a = 40;
            SLEEP(100);
            out << "slept, a=" << a << endl;
            qApp->exit();
        }
    
    private:
        int a; // used in place of automatic variables
    
    private:
        void sleep(int ms, void (Object::*target)()) {
            next = target;
            timer.start(ms, this);
        }
        void timerEvent(QTimerEvent * ev)
        {
            if (ev->timerId() == timer.timerId()) {
                timer.stop(); (this->*next)();
            }
        }
        QTimer periodic;
        QBasicTimer timer;
        void (Object::* next)();
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        Object o1;
    #ifdef Q_WS_WIN
       timeBeginPeriod(1); // timers will be accurate to 1ms
    #endif
        return a.exec();
    }
    
    #include "main.moc"
    
    0 讨论(0)
  • 2020-12-16 04:41

    I agree with jalf. I have a thread that acts as a sort of DBUS daemon and needs to listen to messages forever. Two things to mention:

    jalf has

    void sleep(int ms) { QThread::sleep(ms); }
    

    But this is NOT MILLISECONDS! QThread::sleep() takes seconds. Also, if one is to take this approach, he must also include the QThread lib anyway, so it might be easier to just make the call like this:

    QThread::sleep(seconds);
    

    directly in the code. That way there isn't an extra header file. I ran this and it also works as jalf explained. (putting the calling thread to sleep.)

    0 讨论(0)
  • 2020-12-16 04:44

    For Qt 4.8.0 (the version I'm using), QThread::sleep, QThread::msleep and QThread::usleep have been made public so you can just call them directly. In earlier Qt versions, they were static protected.

    e.g. QThread::sleep(5); // sleep for 5 seconds

    0 讨论(0)
  • 2020-12-16 04:54

    What we've done is basically something like this: (written by memory, as I don't have our code checked out on this computer)

    class Sleeper : public QThread {
    public:
       void sleep(int ms) { QThread::sleep(ms); }
    };
    
    void sleep(int ms);
    
    // in a .cpp file:
    static Sleeper slp;
    
    void sleep(int ms) {
        slp.sleep(ms);
    }
    

    The key is that the QThread::sleep function causes the calling thread to sleep, not the threaf represented by the QThread instance. So just create a wrapper which calls it via a custom QThread subclass.

    Unfortunately, QThread is a mess. The documentation tells you to use it incorrectly. A few blog posts, as you've found, tell you a better way to do it, but then you can't call functions like sleep, which should never have been a protected thread member in the first place.

    And best of all, even no matter which way you use QThread, it's designed to emulate what's probably the worst thread API ever conceived of, the Java one. Compared to something sane, like boost::thread, or even better, std::thread, it's bloated, overcomplicated and needlessly hard to use and requiring a staggering amount of boilerplate code.

    This is really one of the places where the Qt team blew it. Big time.

    0 讨论(0)
提交回复
热议问题