如何解锁运行 pcsc 呼叫的 QThread?



我有一个Qt应用程序,它使用GNU/Linux,MacOS和Windows下的各种pcsc实现连接到读卡器。与卡的所有通信都在工作线程中运行。

在一种情况下,用户开始需要通过读卡器与卡进行通信的操作。读卡器有一个键盘,在身份验证过程中,用户必须在读卡器的键盘上输入他们的 PIN。

此操作通过调用SCardControl()来实现(例如,请参阅Microsoft文档)。只要用户正在使用读取器,对SCardControl()的调用就不会终止,并且工作线程就会被它阻止。

此时,用户可能决定在操作仍处于挂起状态时关闭应用程序。此时关闭应用程序会导致应用程序崩溃(在带有信号SIGABRT的 Linux 上),因为:

  1. 工作线程被阻塞,等待SCardControl()返回。
  2. 主线程无法停止阻塞的线程:quit()terminate()都不会导致线程完成。
  3. 退出应用程序时,将销毁工作线程的QThread对象,并且由于线程仍处于运行状态,因此它会引发一个信号来指示错误。

我已经尝试了几种解决方案。

  1. 子类QThread并创建一个工作线程,该线程调用setTerminationEnabled(true);以允许通过QThread::terminate()终止。这在MacOS上不起作用:当QThread被销毁时,线程仍处于运行状态,并发出信号SIGABRT
  2. 关机时处理信号SIGABRT并忽略它。这似乎不是一个好主意,但我想在丢弃它之前尝试一下。忽略信号SIGABRT后,接收到信号SIGSEGV,应用程序崩溃。我已经调整了这里描述的方法。
  3. 尝试通过从主线程向读卡器发送命令来取消阻止线程。我尝试了SCardCancel()SCardDisconnect()SCardReleaseContext(),但这些命令对阻塞的线程没有任何影响。

我觉得很奇怪,当线程在某些函数调用中被阻塞时,不可能完全关闭应用程序,但是我尝试过的所有解决方案都没有奏效,而且我已经没有想法了。我忽略了什么吗?有人有什么有用的提示吗?

编辑

我查看了QThread的Qt源代码,发现在类Unix平台上,QThread::terminate()内部使用pthread_cancel()。但显然pthread_cancel()不起作用/对Darwin什么都不做,请参阅例如 这里 和 这里.

因此,也许我真的必须选择向用户显示一个对话框,要求从读卡器中删除卡。

如果在调用中被阻止,则无法从外部完全关闭线程。但是,您可以阻止用户在操作完成之前退出应用程序。

void MainWindow::closeEvent(QCloseEvent *closeEvent) {
if (workerBlocked) closeEvent->ignore();
}

此外,还可以显示一个对话框,告知用户必须先完成操作。

此外,如果可能,您可以让窗口关闭,但通过设置qApp->setQuitOnLastWindowClosed(false);使应用程序保持活动状态,直到操作完成

问题归结为这样一个事实,即在关联的线程运行时,QThread对象是不可破坏的。通常,它会在调试输出中像这样打印语句:

QThread:在线程仍在运行时销毁

不要为尝试让SCardControl返回而苦恼,以便可以安全地退出工作线程(因为只要用户与读取器交互,它就不会返回)。相反,您可以按照此答案以安全的方式销毁QThread对象,只需对当前实现进行最少的更改。

这里有一个例子来说明我的意思:

#include <QtWidgets>
//a thread that can be destroyed at any time
//see http://stackoverflow.com/a/25230470
class SafeThread : public QThread{
using QThread::run;
public:
explicit SafeThread(QObject* parent= nullptr):QThread(parent){}
~SafeThread(){ quit(); wait(); }
};
//worker QObject class
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject* parent = nullptr):QObject(parent){}
~Worker(){}
Q_SLOT void doBlockingWork() {
emit started();
//the sleep call blocks the worker thread for 10 seconds!
//consider it a mock call to the SCardControl function
QThread::sleep(10);
emit finished();
}
Q_SIGNAL void started();
Q_SIGNAL void finished();
};

int main(int argc, char* argv[]) {
QApplication a(argc, argv);
//setup worker thread and QObject
Worker worker;
SafeThread thread;
worker.moveToThread(&thread);
thread.start();
//setup GUI components
QWidget w;
QVBoxLayout layout(&w);
QPushButton button("start working");
QLabel status("idle");
layout.addWidget(&button);
layout.addWidget(&status);
//connect signals/slots
QObject::connect(&worker, &Worker::started, &status,
[&status]{ status.setText("working. . .");} );
QObject::connect(&worker, &Worker::finished, &status,
[&status]{ status.setText("idle");} );
QObject::connect(&button, &QPushButton::clicked, &worker, &Worker::doBlockingWork);
w.show();
return a.exec();
}
#include "main.moc"

请注意,SafeThread的析构函数确保wait(),直到关联的线程完成执行。只有之后,主线程才能继续调用QThread的析构函数。

相关内容

  • 没有找到相关文章

最新更新