为什么 QThread 的任务不能在函数返回之前执行。

huangapple go评论52阅读模式
英文:

Why the task of QThread can not be executed until the function returns

问题

以下是您要翻译的代码部分:

例如:

void MainWidget::testThreadTask()
{
    qDebug() << "On test task";
}

void MainWidget::onBtnClick()
{
    QThread *thread = new QThread;
    connect(thread, QThread::started, this, testThreadTask);

    thread->start();
    qDebug() << "Thread START, now we wait 5s";

    QElapsedTimer timer;
    timer.start();
    
    while (timer.elapsed() < 5000)
    {
        
    }

    qDebug() << "END";
}

程序的输出是:

开始等待 5 
结束
在测试任务中

我想在按下按钮后创建一个任务来处理一些事情,然后在任务完成之前等待函数返回。

实际上,可能没有必要创建一个新任务并等待它执行,因为既然必须等待并卡在那里,为什么不直接在函数中运行呢。

但当我处理QT串行数据时,这实际上是一个问题。我想在按下按钮后将数据发送到串行端口,然后等待数据(通过不断读取),但我发现当我一直等待时,串行端口根本无法读取数据,只有当我退出函数时,串行端口才能读取数据。

有没有办法处理串行数据的发送和接收同步问题?

void MainWidget::onBtnClick()
{
    serial->write("测试");

    if (serial->bytesAvailable())
    {
        QByteArray data = serialIo->readAll();
        // 处理数据
    }
}
英文:

For example:

void MainWidget::testThreadTask()
{
    qDebug() &lt;&lt; &quot;On test task&quot;;
}

void MainWidget::onBtnClick()
{
    QThread *thread = new QThread;
    connect(thread, QThread::started, this, testThreadTask);

    thread-&gt;start();
    qDebug() &lt;&lt; &quot;Thread START, now we wait 5s&quot;;

    QElapsedTimer timer;
    timer.start();
    
    while (timer.elapsed() &lt; 5000)
    {
        
    }

    qDebug() &lt;&lt; &quot;END&quot;;
}

The program output is:

START wait 5s
END
On test task

I want to create a task to handle something after the button is pressed, and then the function will wait for the task to complete before returning.

In fact, it may not be necessary to create a new task and wait for it to execute, because since you have to wait and get stuck there, why not run it directly in the function.

But this is actually a problem when I deal with QT serial data. I want to send the data to the serial port after pressing the button, and then wait for the data (by constantly reading), but I find that when I have been waiting, the serial port can not read the data at all, only when I exit the function the serial port can read the data.

Is there any way to deal with serial data sending and receiving synchronization?

void MainWidget::onBtnClick()
{
    serial-&gt;write(&quot;Test&quot;);

    if (serial-&gt;bytesAvailable())
    {
        QByteArray data = serialIo-&gt;readAll();
        // handle the data
    }
}

答案1

得分: 1

关于你的应用程序发生的情况,你有一些误解。我建议你阅读《Threads and QObjects》(整个页面)、《Qt::ConnectionType》以及《QThread的详细描述》。

你所遇到的情况是:

  • MainWidget 不在 thread 中。要从 thread 中调用普通对象的槽函数,首先需要将它移动到该线程。请注意,QWidget 的子类不能被移动到另一个线程中。因为Qt支持的一些操作系统限制窗口的位置,他们选择强制所有QWidget都留在主线程中,无论Qt在哪个操作系统上执行。

  • 当你连接threadthis(顺便说一句,你的问题中是不正确的,应该是使用&符号连接,如connect(thread, &QThread::started, this, &MainWidget::testThreadTask);),你创建了一个排队连接,尽管线程尚未正式启动。

  • 当你启动线程时:

    1. 它会触发它的started信号。
    2. 因为连接是Qt::QueuedConnection,槽函数只会在返回到主线程的事件循环后执行,也就是在onBtnClick返回后的一段时间。

注意事项:

  • 通过使用QThread::currentThread(),你可以在qDebug()中获得更多关于运行代码的线程的有用信息。更好的做法是,你的IDE应该提供一个特定的窗口,用于查看哪个线程达到了断点(在Visual Studio上按Ctrl+Alt+H)。

  • 再次强调一下,要记住Qt帮助中的这个警告:

要知道,如果接收者的线程中正在运行事件循环,使用直接连接时,当发送者和接收者位于不同线程中时是不安全的,原因与在另一个线程中调用对象上的任何函数是不安全的相同。

鉴于你在返回事件循环之前等待了5秒,而且这只是测试代码(应该没有错误,即使有也无所谓),你可以尝试创建一个Qt::DirectConnection,只是为了看到槽函数从工作线程中被调用。

《QThread的详细描述》(上面的链接)展示了一个将工作对象移动到新线程之前的完整工作示例。关键点是:

  1. 创建工作对象,然后将其移动到工作线程。
  2. 为控制器创建连接,通过信号/槽将QString发送到工作对象,还为工作对象返回结果到控制器创建连接,也是通过信号/槽。所有这些连接默认都是Qt::QueuedConnection,因为工作对象已经被移动到了线程中。
  3. 启动工作线程。由于没有重写run函数,它启动了一个事件循环(在exec中)。

就是这样。

记住一点:窗口部件不能被移动!要将你的工作对象创建为一个独立的类。

英文:

You are mistaken about what is happening in your application. I suggest you read Threads and QObjects (the entire page), Qt::ConnectionType and the detailed description of QThread.

What is happening to you is:

  • MainWidget does not live in thread. For the slot of a regular object to be called from thread, it first needs to be moved to that thread.<br/>Note that subclasses of QWidget cannot be moved to another thread. Because some OS supported by Qt limit where windows can live, they made the choice to force all QWidget to stay in the main thread, in all OS Qt can execute on.
  • When you connect thread to this (which BTW is incorrect in your question, it should have been with ampersands connect(thread, &amp;QThread::started, this, &amp;MainWidget::testThreadTask);), you create a queued connection, even though the thread has not technically started yet.
  • When you start the thread:
    1. It fires its started signal.<br/>
    2. Because the connection is a Qt::QueuedConnection, the slot will only be executed after returning to the main thread's event loop, i.e. some time after returning from onBtnClick.

Notes:

  • You would have more useful information in qDebug() about the threads running your code by using QThread::currentThread().<br/>Even better than that, your IDE should provide you a window specifically to see what thread has reached a breakpoint (Ctrl+Alt+H on Visual Studio).
  • At the risk of insisting, keep in mind this warning from the Qt help:<br/>
    Be aware that using direct connections when the sender and receiver live in different threads is unsafe if an event loop is running in the receiver's thread, for the same reason that calling any function on an object living in another thread is unsafe.<br/>
    With that said, because you wait 5 seconds before returning to the event loop and because it is only test code (= there should be no bug + it does not matter even if there is one), you should try to create a Qt::DirectConnection, just to see the slot be invoked from the worker thread.

The detailed description of QThread (link above) shows a complete working example of a worker object being moved to the new thread before it is started. The point is:

  1. A worker object is created, then moved to the worker thread.
  2. Connections are created for the controller to send QString to the worker object via signal/slot and for the worker object to return result to the controller via signal/slot too.<br/>
    All these connections are Qt::QueuedConnection by default since the worker object was moved.
  3. The worker thread is started. Since run was not overriden, it starts an event loop (in exec).

And there you have it.

Remember 1 thing: widgets cannot be moved!!! Create your worker object as a separate class.

huangapple
  • 本文由 发表于 2023年2月8日 17:50:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/75383961.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定