QtConcurrent::run 异常通知

Posted

技术标签:

【中文标题】QtConcurrent::run 异常通知【英文标题】:Notification about exceptions in QtConcurrent::run 【发布时间】:2012-12-05 16:11:47 【问题描述】:

我知道实际处理在不同线程中抛出的异常是没有意义的,但是有什么方法可以让我得到至少发生异常的通知?例如。像

#include <QtConcurrentRun>

#include <iostream>
#include <stdexcept>

void MyFunction()

//  std::cout << "MyFunction()" << std::endl;
  throw std::runtime_error("Test exception.");


int main()

  try
  
    QtConcurrent::run(MyFunction);
  
  catch(...)
  
    std::cout << "Exception caught!" << std::endl;
  


即使发生异常,也会安静地退出。当异常来自调用堆栈深处的某个地方时,这有时会非常令人困惑。

------------编辑----------------

我尝试像 UmNyobe 建议的那样编写一个包装器,但我一定是函数指针有问题?

#include <QtConcurrentRun>
#include <QFutureWatcher>
#include <QObject>

#include <iostream>
#include <stdexcept>

void MyFunction()

//  std::cout << "MyFunction()" << std::endl;
  throw std::runtime_error("Test exception.");


template<typename TFirstParam, typename... TParams>
bool ExceptionWrapper(TFirstParam firstParam, TParams&& ...params)

  // Here 'firstParam' should be a function pointer, and 'params' are the arguments
  // that should be passed to the function
  try
  
    firstParam(params...);
  
  catch(...)
  
    std::cout << "Exception caught!" << std::endl;
    return false; // failure
  

  return true; // success


struct MyClass : public QObject

  Q_OBJECT

  MyClass()
  
    connect(&this->FutureWatcher, SIGNAL(finished()), this, SLOT(slot_finished()));
  

  void DoSomething()
  
    void (*myFunctionPointer)() = MyFunction;
    bool (*functionPointer)(decltype(myFunctionPointer)) = ExceptionWrapper;

    QFuture<bool> future = QtConcurrent::run(functionPointer);
    this->FutureWatcher.setFuture(future);
  

  QFutureWatcher<void> FutureWatcher;

  void slot_finished()
  
    std::cout << "Finished" << std::endl;
    if(!this->FutureWatcher.result())
    
      std::cout << "There was an error!" << std::endl;
    
  
;

#include "ExceptionWrapper.moc"

int main()

  MyClass myClass = new MyClass;
  myClass->DoSomething();

我得到的错误在这一行:

QFuture<bool> future = QtConcurrent::run(functionPointer);

error: no matching function for call to 'run(bool (*&)(void (*)()))'

【问题讨论】:

有一个问题。调用线程有可能在异常发生之前就完成了受保护代码的执行。 UmNyobe - QFuture 怎么样? 在 qt4 中运行需要一个对象... 【参考方案1】:

我知道实际处理在不同线程中抛出的异常是没有意义的,但是有什么方法可以让我得到至少发生异常的通知?

您可以使用从QtConcurrent::run 返回的未来来处理它。有关详细信息,请参阅this page。当您在未来收集时,将重新抛出任何未处理的异常。您可以创建一个简单的包装类来捕获异常并在接收线程中对其进行检查。

#include <QtGui>
#include <iostream>
#include <stdexcept>

class MyException : public QtConcurrent::Exception

public:
    MyException(std::exception& err) : e(err) 
    void raise() const  throw *this; 
    Exception* clone() const  return new MyException(*this); 
    std::exception error() const  return e; 
private:
    std::exception e;
;

// first concurrent function
int addFive(int n)

    try
    
        throw std::runtime_error("kablammo!");
        //throw -1;
        return n + 5;
    
    catch (std::exception& e)
    
        throw MyException(e);
    



// second concurrent function    
void myVoidFunction()

    try
    
        throw std::runtime_error("oops!");
        //throw -1;
    
    catch (std::exception& e)
    
        throw MyException(e);
    


int main(int argc, char* argv[])

    QApplication app(argc, argv);

    QFuture<int> f1 = QtConcurrent::run(addFive, 50);
    try
    
        int r = f1.result();
        std::cout << "result = " << r << std::endl;
    
    catch (MyException& me)
    
        std::cout << me.error().what() << std::endl;
    
    catch (QtConcurrent::UnhandledException&)
    
        std::cout << "unhandled exception in addFive\n";
    

    QFuture<void> f2 = QtConcurrent::run(myVoidFunction);
    try
    
        // result() not available for QFuture<void>, use waitForFinished() to
        // block until it's done.
        f2.waitForFinished();
        std::cout << "myVoidFunction finished\n";
    
    catch (MyException& me)
    
        std::cout << me.error().what() << std::endl;
    
    catch (QtConcurrent::UnhandledException&)
    
        std::cout << "unhandled exception in myVoidFunction\n";
    

    QWidget w;
    w.show();

    return app.exec();

【讨论】:

测试中,谢谢!请参阅上面对我的答案的编辑-即使不使用 QtConcurrent::Exception,似乎也会重新抛出异常?你能确认我在那里所做的事情是有道理的(它似乎有效)吗? @DavidDoria 是的,这是有道理的。 Qt 会记住并发线程中抛出的未处理异常,然后在您将来收集时重新抛出它(使用result()waitForFinished() 等)。如我的回答所示,使用包装器获得的唯一好处是,您可以检查调用线程中的异常(我认为这是您的问题所问的?)。当然,这需要您捕获它并将其存储在并发线程中。如果您实际上并不关心异常的内容,那么您编辑的答案就很好。【参考方案2】:

似乎如果抛出异常,关联的QFutureWatcher的isCanceled()返回true:

#include <QApplication>
#include <QtConcurrentRun>
#include <QFutureWatcher>

#include <iostream>
#include <stdexcept>

void MyFunction()

  std::cout << "MyFunction()" << std::endl;
  throw std::runtime_error("Test exception.");


struct MyClass : public QObject

  Q_OBJECT

public:
  MyClass()
  
    connect(&this->FutureWatcher, SIGNAL(finished()), this, SLOT(slot_finished()));
  

  void DoSomething()
  
    QFuture<void> future = QtConcurrent::run(MyFunction);
    this->FutureWatcher.setFuture(future);
  

  QFutureWatcher<void> FutureWatcher;

public slots:
  void slot_finished()
  
    std::cout << "Finished" << std::endl;
    if(this->FutureWatcher.isCanceled())
    
      std::cout << "There was an error!" << std::endl;
    
    else
    
      std::cout << "Success!" << std::endl;
    
  
;

#include "Exception.moc"

int main(int argc, char*argv[])

  MyClass myClass;
  myClass.DoSomething();

  QApplication app(argc, argv);

  return app.exec();

--------- 编辑(戈登弗里曼答案的简化版)---------

即使不使用 QtConcurrent::Exception 子类,似乎也会重新抛出异常?

#include <QtGui>
#include <iostream>
#include <stdexcept>

// non-void concurrent function
int addFive(int n)

  throw std::runtime_error("addFive throw!");
  return n+5;


// void concurrent function
void myVoidFunction()

   throw std::runtime_error("myVoidFunction throw!");


int main(int argc, char* argv[])

    QApplication app(argc, argv);

    QFuture<int> f1 = QtConcurrent::run(addFive, 50);
    try
    
        int r = f1.result();
        std::cout << "result = " << r << std::endl;
    
    catch (...)
    
      std::cout << "exception in addFive." << std::endl;
    

    QFuture<void> f2 = QtConcurrent::run(myVoidFunction);
    try
    
        // result() not available for QFuture<void>, use waitForFinished() to
        // block until it's done.
        f2.waitForFinished();
        std::cout << "myVoidFunction finished\n";
    
    catch (...)
    
       std::cout << "exception in myVoidFunction\n";
    

    QWidget w;
    w.show();

    return app.exec();

【讨论】:

【参考方案3】:

QtConcurrent::run 的好处是它接受带有返回值的函数。 我的两分钱:尽早捕获异常...

免责声明:C++ 中的异常我真的很糟糕:D

在不同线程中调用的代码应该返回一个值。对于 void 函数或不捕获异常的现有函数,您可以定义一个包装器(通用或非通用)。比如

 int exceptionwrapper()
   int exception  = 0;
   try
   
      myFunction();
   
   catch(...)
      exception = 1;
      std::cout << "Exception caught!" << std::endl;
   
   return exception;
 

后来

 QFuture<int> future = QtConcurrent::run(exemptionwrapper);
 futurewatcher.setFuture(future);

当函数结束时,您只需使用未来观察器就可以在以后检查未来。

【讨论】:

UmNyobe - 我明白你在说什么。如果我有很多这样的函数要调用,这可能会产生相当高的成本。似乎应该有一种更简单的方法来有效地将“throw myException”转换为“cout 是的,它的成本相当高。如果您是模板大师,您可以将包装器编写为与QtConcurrent::run 相同形式的模板,它总是返回一个包含异常类型和返回值的对象被调用者的。或者你可以使用 movetothread 和信号和槽... 我已经添加了对这样一个包装器的尝试作为对问题的编辑。显然我对函数指针做错了什么?结构看起来好吗? (我将所有内容都放在一个类中,以便 FutureWatcher 能够持续存在,因为我们没有在此演示中进入事件循环)。你能看出我做错了什么吗?

以上是关于QtConcurrent::run 异常通知的主要内容,如果未能解决你的问题,请参考以下文章

使用 QtConcurrent::run() 修改成员变量?

停止由 QtConcurrent::run 启动的线程?

QtConcurrent::run 发出信号

在 QtConcurrent::run 中使用 QSqlDatabase 连接(伪连接池)

是否可以将 QtConcurrent::run() 与类的函数成员一起使用

如何将 QtConcurrent::run 函数(或类似函数)中的 progressText 传递给 QFutureWatcher?