来自Lua脚本的nanosleep()调用暂停了QT GUI线程

时间:2011-10-19 08:40:54

标签: c++ c qt4 lua

我正在开发一种测试工具,用于从PC并行端口生成波形。此工具旨在生成任何波形模式,定时精度为ms,因此我使用Lua脚本定义波形模式,当用户单击[开始]按钮时,GUI启动新的QThread以运行脚本。

Lua的以下三个函数实现为C ++全局函数:

  • pwrite:将数据写入并行端口。
  • msleep:等待某个ms(使用nanosleep()实现)
  • print:覆盖Lua默认打印功能,此功能会将消息附加到一个QTextEdit小部件。

当调用pwrite时,写入的数据存储在全局变量中,然后以20ms的间隔更新GUI以更新GUI上的并行端口数据。 (这20ms间隔刷新不是一个好的设计,但我还没弄清楚如何在数据发生变化时使用信号进行GUI更新。)

该工具现在基本上可用了。波形输出没有问题,但并行端口数据更新有一些问题:

当Lua调用msleep时,GUI线程停止,并行端口数据仅在msleep结束后更新。

所以我的问题是:

  1. 如何实现sleep方法,以免它停止GUI线程的更新?

  2. 如何实现pwrite,以便在写入数据发生变化时GUI可以接收更新并行端口数据的信号?

  3. 程序GUI如下链接: Program GUI

    相关代码:

        /* common.cpp file */
    
    int L_MSleep(lua_State* l)
    {
        int milisec=0;
        struct timespec req={0, 0};
        time_t sec;
    
        milisec=luaL_optint(l,1,0); // obtain parameter
    
        if (milisec==0)
           return 0;
    
        sec=(int)(milisec/1000);
    
        milisec=milisec-(sec*1000);
        req.tv_sec=sec;
        req.tv_nsec=milisec*1000000L;
    
        while(nanosleep(&req,&req)==-1)
             continue;
    
        return 1;
    }
    
    
    /* LuaRunner.cpp file */
    LuaRunner::LuaRunner(QObject *parent) :
        QThread(parent)
    {
        runlua = false;
    }
    
    void LuaRunner::run()
    {
        QString err = "";
    
        runlua = true;
        LUA_RunScript(this->ff, err);
        runlua = false;
    
        if(err != "")
        {
            emit errorMessage(err);
        }
    }
    
    int LuaRunner::LUA_RunScript(QString ff, QString &err)
    {
        L = lua_open();
        luaL_openlibs(L);
    
        if (luaL_loadfile(L, ff.toAscii()) || lua_pcall(L, 0, 0, 0))
        {
            err = QString(lua_tostring(L, -1));
            return -1;
        }
    
        lua_register(L, "ssleep", L_SSleep);
        lua_register(L, "msleep", L_MSleep);
        lua_register(L, "pwrite", L_PortWrite);
        lua_register(L, "print", L_Log);
    
        lua_getglobal(L, "dotest");
        if (!lua_isfunction(L, -1))
        {
            err = QString("Test function(dotest) should be a function");
            return -1;
        }
    
        if(lua_pcall(L, 0, 0, 0))
        {
            err = QString(lua_tostring(L, -1));
            return -1;
        }
    
        lua_close(L);
    
        return 0;
    }
    

2 个答案:

答案 0 :(得分:4)

您在专用线程中正确运行Lua脚本。这是正确的方法 - 差不多。每次要运行脚本时都要重新启动线程。那是错的。您还从LUA线程访问GUI线程中的数据,而不进行任何同步。这不好。 Qt以信号和插槽之间的排队连接的形式提供了一种出色的机制。当信号槽调用通过线程边界时,参数将被包装在QEvent中,并异步传递给目标QObject。在每个线程中,事件传递是序列化的,因此您不必担心数据损坏等。

以下是应该如何做的:

// LUAObject.h
#include <QObject>

class LUAObject : public QObject
{
   Q_OBJECT
public:
   LUAObject(QObject * parent = 0);
public slots:
   void setScript(const QString &);
   void runScript();
   void stop();

signals:
   void hasError(const QString &);
   void finished();
   void hasParallelData(int);
   void hasMessage(const QString &);

private:
   QString script;
   bool stop;
}

// LUAObject.cpp

// whatever Lua includes you need etc

LUAObject::LUAObject(QObject* p) : QObject(p)
{}

void LUAObject::stop() { stopped = true; }    

void LUAObject::setScript(const QString & scr)
{ script = scr; }

int L_PWrite(lua_State* l)
{
   int data = luaL_optint(l, 1, -1);
   if (data != -1) {
      // access the parallel port HERE, NOT in the GUI thread!
      emit hasParallelData(luaL_optint(l, 1, 0));
   }
   return 0;
}

// returns a bool - true means we are stopped and should exit
int L_MSleep(lua_State* l)
{
   int ms = luaL_optint(l, 1, -1);
   if (ms == -1) return 0;
   QApplication::processEvents(QEventLoop::WaitForMoreEvents, ms);
   lua_pushBoolean(l, stopped); // event processing would run the stop() slot call
   return 1;
}

int L_SSleep(lua_State* l)
{
   int secs = luaL_optint(l, 1, -1);
   if (secs == -1) return 0;
   QApplication::processEvents(QEventLoop::WaitForMoreEvents, secs*1000);
   lua_pushBoolean(l, stopped); // event processing would run the stop() slot call
   return 1;
}

int L_Log(lua_State* l)
{
   const char * msg = luaL_optstring(l, 1, 0);
   if (!msg) return 0;
   emit hasMessage(msg);
   return 0;
}

class Lua // RAII
{
public:
   explicit Lua(lua_state * l) : L(l) {}
   ~Lua() { lua_close(L); }
   operator lua_state*() const { return L; }
private:
   lua_state * L;
   Q_DISABLE_COPY(LUA)
};

LUAObject::runScript()
{
   stopped = false;
   Lua L(lua_open());
   luaL_openlibs(L);

   if (luaL_loadbuffer(L, script.toAscii().constData(), script.length(), "script") || lua_pcall(L, 0, 0, 0))
   {
       emit hasError(lua_tostring(L, -1));
       return;
   }

   lua_register(L, "ssleep", L_SSleep);
   lua_register(L, "msleep", L_MSleep);
   lua_register(L, "pwrite", L_PWrite);
   lua_register(L, "print", L_Log);

   lua_getglobal(L, "dotest");
   if (!lua_isfunction(L, -1))
   {
      emit hasError("Test function(dotest) should be a function");
      return;
   }

   if(lua_pcall(L, 0, 0, 0))
   {
      emit hasError(lua_tostring(L, -1));
      return;
   }

   emit finished();
}

// main.cpp

#include <QApplication>
#include <QMetaMethod>
#include "LUAObject.h"

...

int main(int argc, char** argv)
{
   QApplication(argc, argv);

   MainWindow window;

   ...
   QThread thread;
   LUAObject lua;
   thread.start(QThread::TimeCriticalPriority);
   lua.moveToThread(&thread);

   ...

   // NOTE: you can ONLY connect to LUAObject slots, you CANNOT call them
   // directly since it runs in a separate thread!
   connect(&window, SIGNAL(startClicked()), &lua, SLOT(runScript());
   connect(&lua, SIGNAL(hasError(QString)), &window, SLOT(luaError(QString)));

   ...
   window.show();
   int rc = qApp->exec();
   QMetaObject::invokeMethod(&lua, SLOT(stop())); // cross-thread slot invocation
   thread.exit();
   thread.wait();
   return rc;
}

我将UI的实现留给您的想象力。请注意,它是未经测试的代码。如果我知道的话,它可能会炸毁你的电脑。

答案 1 :(得分:0)

也许您应该使用QT的msleep,因为它是QThread提供的用途