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

发布于 2024-12-10 17:18:12 字数 2182 浏览 0 评论 0原文

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

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

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

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

目前该工具已经基本可以使用了。波形输出没有问题,但是并口数据更新有问题:

当Lua调用msleep时,GUI线程被停止,并口数据只有在msleep结束后才更新。

所以我的问题是:

  1. 如何实现 sleep 方法,使其不会阻止 GUI 线程更新?

  2. 如何实现pwrite,让GUI在写入数据改变时可以收到信号来更新并口数据?

    如何

程序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;
}

I am developing a test tool to generate waveform from PC parallel port. This tools is designed to generate any pattern of waveform with timing accuracy of ms, so I use Lua script to define the waveform pattern, the GUI start new QThread to run the script when user clicks [Start] button.

The following three functions for Lua are implemented as C++ global functions:

  • pwrite: write data to parallel port.
  • msleep: wait for certain ms (implemented using nanosleep())
  • print: overwrite Lua default print function, this one will append message to one QTextEdit widget.

when pwrite is called, the written data is stored in global variable, then the GUI is updated with 20ms interval to update the parallel port data on the GUI. (this 20ms interval refresh is not a good design, but I haven't figure out how to use signal to make GUI update when data changed).

The tool is basically functional now. The waveform output has no problem, but the parallel port data updating has some problem:

When Lua call msleep, GUI thread is stopped, the parallel port data updates only after msleep ends.

So my questions are:

  1. How to implement the sleep method so that it won't stop the GUI thread from updating?

  2. How to implement the pwrite, so that the GUI can receive a signal to update the parallel port data when written data changed?

Program GUI as the link below:
Program GUI

The related code:

    /* 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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

┼── 2024-12-17 17:18:12

您在专用线程中正确运行 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 的实现留给您想象。请注意,这是未经测试的代码。据我所知,它可能会炸毁你的计算机。

You correctly run your Lua script in dedicated thread. That's the right way to do it -- almost. You are restarting the thread each time you want to run the script. That's wrong. You are also accessing the data in the GUI thread from the LUA thread without any sort of synchronization. That's not good. Qt provides an excellent mechanism for that in the form of queued connections between signals and slots. When signal-slot calls pass thread boundaries, the parameters get wrapped in a QEvent and get asynchronously delivered to the target QObject. Within each thread, the event delivery is serialized and thus you don't need to worry about data corruption etc.

Here's how it should be done:

// 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;
}

I leave the implementation of the UI to your imagination. Note that it's untested code. It may blow up your computer for all I know.

御守 2024-12-17 17:18:12

也许您应该使用 QT 的 msleep 因为它是由 QThread

Perhaps you should use QT's msleep since it is provided for use by QThread

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文