Results 1 to 4 of 4

Thread: Exceptions and qApp->processEvents()

  1. #1
    Join Date
    Jan 2006
    Posts
    31
    Thanked 5 Times in 4 Posts
    Qt products
    Qt3 Qt4
    Platforms
    Unix/X11

    Default Exceptions and qApp->processEvents()

    Hi all,

    I have gone through some difficulties with qApp->processEvents() in Qt3, I have
    written some code I would like to share in the hope it will be useful to someone.

    In the following text there is the problem explanation and a brief on how I tried to solve it.

    I don't know if it is the best/correct way to deal with, I didn't found material on exceptions handling in a Qt environment.

    Please, feel free to comment on it, it is still a draft and any suggestion is welcomed.

    If someone find it interesting I can post the (working) code.


    Thanks
    Marco




    THE PROBLEM


    Qt3 uses the call qApp->processEvents() (from now on PE) to let a program do not block while a long operation is occurring. This function passes the control to the application event loop and any new pending event like GUI event, socket notifier(QProcess) and others are processed.

    The problem with PE is that when the calling function returns a changed context could
    be found. Some objects could be born, other disappeared, some action incompatible
    with the previous context could have been done, as example closing a window while
    processing or clearing a data container that the calling function is using.

    How to deal with this? A way is to check some 'context flags' on return from
    PE to search for a 'wrong context' and take appropriate actions.
    But this is not a good general solution because implies that the function that calls, and
    returns, from PE knows about the whole context.

    As example, A() is a function that access some data (kept in any kind of container a list, a
    file, etc), B() is a function that makes a lengthy operation and uses PE.
    If we have a code flow like the following

    A()
    |---> B()
    | |-----> qApp->processEvents()
    | |-----> do something else
    | return
    |---> ....
    |---> data.access()

    We should check inside of B() if the data is no more valid and eventually return an error code.
    But B() knows nothing about the our data. Of course we should check in A(), just after the B() call,
    but this is sub-optimal because it implies that we know for sure that B() does not accesses the data
    _nor_ any other function called by B() before to return to A().

    In real software the call chain between the function that uses a resource and the function that
    pass the control to the application event loop and return from it can be very long and complex.



    INTRODUCING EXCEPTIONS

    The standard way that C++ has to deal with this kind of problems is called exceptions handling.

    If B() throws an exception, returning from PE, on a corrupted database, and the first catch clause is in A(), no matter how long and complex the chain is, A() will catch the exception before anything else is done.

    This seems interesting, but has two drawbacks, one general, one depending on Qt.


    -Exception resuming/termination models

    What happens if B() is called on start up, on data refreshing, or, in general, in a context where database consistency is not important or, worst, should not be checked? The exception will be thrown, no catch clause will take the exception, the default handler will be invoked and this means, at least in C++, program termination [in C++ you can provide a custom et_terminate() function to handle uncaught exceptions but you cannot continue from here, just clean-up and exit].
    The general problem has a name and is called 'C++ supports termination-model exceptions, not resumption'.

    So we need a way to let B() throw an exception _only_ if the exception will be caught, IE
    only if the abnormal condition is 'interesting' in the actual context.


    -Exceptions trough signals/slots

    Standard C++ try-throw-catch exception system is not compatible with Qt signals/slots. If a function B() is called by A() by means of a signal the catch clause will not work, also if signals/slots in Qt3 are wrappers to direct calls.

    A()
    |---> try
    |---> emit mySignal --> slotMySignal()
    | |-----> throw X
    |---> catch(x)
    | |---> we will NOT catch X


    It is possible to code to avoid signals/slots when exceptions are involved, but the _real_ problem is that also PE is a barrier to exceptions propagation.

    What happens is the following:

    A()
    |---> try
    | B()
    | |-----> qApp->processEvents()
    | | |----> C()
    | | |----> database.clear()
    | | throw databaseIsEmpty
    | |<----- qApp->processEvents()
    | |
    | <----return
    |
    |---> catch(databaseIsEmpty)
    | |
    | |---> we will NOT catch databaseIsEmpty


    This is very unfortunate.



    INTRODUCING EXCEPTION MANAGER

    If we rewrite the above scheme as follows:

    A()
    |---> try
    | B()
    | |-----> qApp->processEvents()
    | | |----> C()
    | | |----> database.clear()
    | | throw databaseIsEmpty
    | | |
    | |<----- qApp->processEvents()<----return
    | |
    | if (databaseIsEmpty is throwable)
    | throw databaseIsEmpty
    | <----return
    |
    |---> catch(databaseIsEmpty)
    | |
    | |---> NOW we will catch databaseIsEmpty



    Two things have changed between the schemes.

    - The potential exception is checked to verify if it is among the throwables exceptions

    - The exception is thrown in the same region* of the catch clause


    *[A 'region' is the code that executes between two calls of PE]


    Class ExceptionManager does exactly this, checks the exception against a throwable set, wait until the correct region is reached and finally throws the exception.

    If we rewrite the above code to use ExceptionManager helper macros we have:

    A() {
    .....
    try {
    EM_REGISTER(databaseIsEmpty); // adds databaseIsEmpty to the throwable set

    .....
    B();
    .....

    EM_REMOVE(databaseIsEmpty); // removes databaseIsEmpty from the throwable set

    } catch (int i) {

    EM_REMOVE(databaseIsEmpty);

    if (i == databaseIsEmpty) {

    .....handle the exception....

    EM_CHECK_PENDING; // re-check any other pending exception
    }
    }
    .....
    }

    B() {
    .....
    EM_BEFORE_PROCESS_EVENTS; // some magic occurs ;-)

    while(something_happens)
    qApp->processEvents();

    EM_AFTER_PROCESS_EVENTS; // throws the pending exceptions belonging to the current region
    .....
    }

    C() {
    .....
    database.clear();
    EM_RAISE(databaseIsEmpty); // checks if databaseIsEmpty is throwable and, in case,
    ..... // flags it as 'raised'. In the latter case it will be
    ..... // thrown, but only when returning in the correct region.
    }


    With this scheme everything works as expected. There are some things to note:

    1) In B() there is no knowledge of 'databaseIsEmpty'. B() does not have to know about the general context at all.

    2) At the end of the catch clause any other pending exception will be thrown, so to allow for
    multiple raised exceptions.

    3) The same exception will be thrown as many times as has been registered. What it means is ExceptionManager supports nested try-catch blocks, also when looking for the same exception, in this case each catch clause will be called with the same exception and in the correct order.

    4) ExceptionManager has an internal multi-region stack. What it means is that try-catch blocks can be nested _across_ many PE calls: each catch clause will be called with the correct raised exception and in the correct time, when returning in the corresponding region from a PE call. No matter when the exceptions have been raised.

  2. #2
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    5,372
    Thanks
    28
    Thanked 975 Times in 912 Posts
    Qt products
    Qt3 Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Exceptions and qApp->processEvents()

    Quote Originally Posted by mcostalba
    A()
    |---> try
    |---> emit mySignal --> slotMySignal()
    | |-----> throw X
    |---> catch(x)
    | |---> we will NOT catch X
    Consider this little program:
    Qt Code:
    1. #include <qobject.h>
    2.  
    3. class A : public QObject
    4. {
    5. Q_OBJECT
    6. public:
    7. void foo()
    8. {
    9. try {
    10. emit someSignal();
    11. }
    12. catch( int i ) {
    13. qWarning( "exception caught i=%d", i );
    14. }
    15. }
    16.  
    17. signals:
    18. void someSignal();
    19. };
    20.  
    21. class B : public QObject
    22. {
    23. Q_OBJECT
    24. public slots:
    25. void someSlot()
    26. {
    27. throw 1234;
    28. }
    29. };
    30.  
    31. int main()
    32. {
    33. A a;
    34. B b;
    35.  
    36. QObject::connect( &a, SIGNAL( someSignal() ), &b, SLOT( someSlot() ) );
    37. a.foo();
    38.  
    39. return 0;
    40. }
    41.  
    42. #include "main.moc"
    To copy to clipboard, switch view to plain text mode 
    Output:
    $ ./except
    exception caught i=1234
    Quote Originally Posted by mcostalba
    the _real_ problem is that also PE is a barrier to exceptions propagation.
    I wonder if event queue would survive this. An exception would abort QApplication::processEvents() right in the middle.

    Anyway, you are right that there is a problem with exceptions in Qt-based applications.

    IMO the best way is to divide the application into two parts. One that implements the functionality (model, core, engine or whatever) and second one that is responsible for the GUI. Of course you should avoid exceptions in the latter.

    The code might look like this:
    Qt Code:
    1. void SomeWidget::someSlot()
    2. {
    3. try {
    4. core::SomeObject::instance().doSomething();
    5. }
    6. catch( const core::Exception& e ) {
    7. MessageBox::exception( e );
    8. }
    9. }
    To copy to clipboard, switch view to plain text mode 

    Where MessageBox is a class that represents a message box informing user about a failure and core::Exception is a base class for all exceptions that can be passed to GUI part.

  3. #3
    Join Date
    Jan 2006
    Posts
    31
    Thanked 5 Times in 4 Posts
    Qt products
    Qt3 Qt4
    Platforms
    Unix/X11

    Default Re: Exceptions and qApp->processEvents()

    In my case, the need for all this exception machinery came from the fact that QT3 does not allow to use a QProcess in a thread because QThread does not have an event loop.

    My core part functions are similar to this one:

    foo()
    result1 = run() ;// call external program executer

    input2 = g(result1);

    result2 = run(input2);

    input3 = h(result2);

    result3 = run(input3);

    return result3;

    where run() launches a process and waits on

    while(busy)
    qApp->processEvents();

    'busy' is reset in processExit(). I want a simple semantics for run() function, without callbacks and splitted launch/exit with async execution, this is the reason of the while loop, and, in any case , also with callbacks on processExit() the problem is not changed, I need to test the context when returning from processExit().

    You suggest to avoid exceptions in GUI part. But exceptions, at least in my case, came _all_ from GUI part: resetting, closing windows, refreshing, opening a directory/archive while core is updating another, changing row selection while core is still processing the current one, and so on, the list is long.

    I have tried to filter out events with QEventLoop::ExcludeUserInput during updating, but the results are sub-optimal, missing drag&drop or drag started but not ended(missing mouse relase button event) and so on.

    The problem is that QEventLoop::ExcludeUserInput does not queue events to be processed later, but filter them outs forever.

    I have googled around before to re-invent the wheel, but I wasn't able to find anything.

    Thanks for yous feedback.
    I am very interested to hear from you what do you think about this.

    Marco

  4. #4
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    5,372
    Thanks
    28
    Thanked 975 Times in 912 Posts
    Qt products
    Qt3 Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Exceptions and qApp->processEvents()

    Quote Originally Posted by mcostalba
    My core part functions are similar to this one:

    foo()
    result1 = run() ;// call external program executer
    [...]
    where run() launches a process and waits on

    while(busy)
    qApp->processEvents();
    I would say that it violates the event-driven programming model. I think you need something like a state machine that runs an external program when it enters some state and leaves that state when it receives the QProcess::processExited() signal.

    IMO with proper separation between user interface and the program state, there shouldn't be any problems with loosing the context.

    Quote Originally Posted by mcostalba
    You suggest to avoid exceptions in GUI part.
    Yes and in fact you can live without exceptions (although they are very usefull, when used in the right way).

    Quote Originally Posted by mcostalba
    resetting, closing windows, refreshing, opening a directory/archive while core is updating another
    There is always a problem what should be treated as exception. Personally I wouldn't call above situations as exceptional. If user did something it wasn't an accident --- he did what the GUI allowed him to do.

    Qt Code:
    1. void SomeForm::on_someButton_clicked()
    2. {
    3. if( _core.isBusy() ) {
    4. MessageBox::busy( "Please wait until some operation is finished." );
    5. return;
    6. }
    7. // do something with _core
    8. }
    To copy to clipboard, switch view to plain text mode 

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.