app.doevents vs. app.yieldtonextthread

I have to admit that for roughly a decade I have been using app.doevents to provide running rbscripts, and now xojoscripts, with access to user input and to update the GUI during a running script. I have been forced to do this because I was not clever enough to come up with alternatives. When I compiled my program in carbon, things worked well and both my users and I had no problems. However, in cocoa, I have discovered that app.doevents does indeed create instability and can render the GUI inoperative. I know this instability has been predicted all along, but now I have verified that in my cocoa applications, it is indeed catastrophic. If I am going to provide a decent cocoa application, I will have to get rid of app.doevents.

However, when I run my xojoscript in a thread, calling context methods that interact with the GUI generates a ThreadAccessingUIException. Because I do this a lot, putting my xojoscript into a thread is not possible. Thus, I need to create a thread that I can yield to that somehow does roughly the equivalent of app.doevents, but does not use app.doevents. Then I can use app.yieldtonextthread to exercise the GUI. I have tried all sorts of threads, but all the threads that show promise also generate a ThreadAccessingUIException for obvious reasons. I need to provide a replacement for app.doevents that hands over time to the GUI, and I am at a loss as to how I can do this.

Simply put, Xojo will not allow threads to access, even indirectly, the GUI, and while providing app.doevents which serves the purpose, strongly recommends against its usage. Help!

I would welcome suggestions for how to get around this catch 22.

Thanks,
Bob

Take a look at the two examples in Examples/Desktop/UpdatingUIFromThread/

These are the two ways that you can update UI directly via a Thread. The trick is that the timer action event in the thread ALWAYS fires on the main thread. Thus any event you call from that Thread class is on the main thread and you can update your UI.

Trust me, the first time you use it will have to be a leap of faith. Once you use it a few times it will be no big deal.

Thanks Bob. I will give this a try!

Well, the thread+timer approach did not work for me. When a Xojoscript is running, and I call a method within it to update the canvas, if I include app.doevents it works just fine, but when I use the method to launch a thread to activate a timer to update the canvas, the timer fires, but the update does nothing that is visible. Only when the xojoscript ends does the process update.

I may well be doing something wrong, but the fact that app.doevents works, but the thread+timer does not, is frustrating. I added canvas.invalidate in the timer action, but that also did nothing. I have confirmed the timer is firing, but nothing I do inside the timer updates the gui until the xojoscript stops.

Any other ideas?

You’re doing something wrong, but it’s not obvious what it is. It sounds like your XojoScript is trying to “push” something to the canvas, which is wrong. The canvas should only do drawing using the Paint event. Your XojoScript should activate the timer, the timer invalidate the canvas, and the canvas Paint should update the contents. This is of course very broad, but it’s hard to advise without specifics.

Dear Thom,

I only use the paint event of the canvas to update the canvas. What I have tried to do is create a thread which calls the timer. Then I try and update the gui by replacing my calls to app.doevents (which work fine in carbon) with app.YieldToNextThread, which does indeed run the thread. The Thread then starts the timer. The timer fires and calls canvas.invalidate. But the paint event does not fire until the xojoscript is done. That is the problem.

My view of the problem is that I am running the xojoscript in the main loop, and the timer is in the main loop. I fear that the timer and the xojoscript, sharing the same loop, cannot run simultaneously. Hence, all updating waits till the xojoscript is done. There is no mechanism I have found to pause the xojoscript so that other pending events in the main loop will fire.

If you have any suggestions, I would welcome them. Thanks for your help.

Ah, that helps.

First of all, App.YieldToNextThread is only very rarely needed. Since Xojo automatically yields on loop boundaries, explicit yielding is rarely necessary.

Second, since your XojoScript is the worker, that’s what needs to be run from within the thread. However, what is not clear to me is wether or not XojoScripts will yield. If they do not, there is nothing you can do within the context of your application. If XojoScripts do not yield, you will need to execute the script in a helper app. I believe they do yield, but I can’t find confirmation of that in the docs.

If they yield, the solution is generally simple. You’ll create a thread subclass with a xojoscript property. You can use AddHandler to map the XojoScript events such as Input and Print to methods on the thread. This will allow your script to report status information back to the thread, which can make that information available to outside sources however you feel appropriate.

Then you use a timer to continuously poll the thread status and invalidate the canvas as necessary. The canvas paint gets the data it needs from the thread.

The goal is to make the thread like a miniature program. Try to “self contain” as much of the code as possible. The thread should never tell your application what to do, your application should react to the thread.

Thom,

I have already investigated putting my xojoscript inside a thread. It works beautifully in earlier versions of RB and Xojo, but no longer works in the more recent versions because calling context methods that interact with the GUI generates a ThreadAccessingUIException. Because I do this a lot, putting my xojoscript into a thread is no longer possible even though it seemed to work fine. To be honest, I think the ThreadAccessingUIException makes no sense if one can use a thread to start a timer to do the identical thing. Calling a context method to access the UI or activating a time to access the UI seems to provide the same degree of separation, but one is allowed and the other is not.

What is frustrating for me is that I cannot use app.doevents (which works in carbon) nor can I run my xojoscripts in a thread (which used to work, but now throws a ThreadAccessingUIException). My friends who use Visual Basic all claim that app.doevents should work, but based on the discussions in the forum, the VB version and the Xojo version all do much the same thing, but the Xojo version causes damage while the VB version was written to be safe.

Xojo is not Visual Basic and app.doevents was never intended for desktop applications. The ThreadAccessingUIException is there for a purpose. Just because it seemed to work fine in older versions doesn’t mean it was the right thing to do.

Honestly, it sounds like you’re not really using the TaskThread as intended. The whole point is that your code runs in the thread and occasionally spits out data on the main thread via an event (since timers fire on the main thread) which loads your GUI.

Now I don’t use XojoScript so it’s possible there’s something funky going on there but I can tell you that you if you are using App.DoEvents you’re doing something really wrong.

Perhaps this is where you come up with an example project doing something simple from the thread and updating the UI. I suspect you’ll learn more that way than trying to force your existing app into a new paradigm. Get the easy win first and then tackle the tough part.

As you write yourself, you call methods that interact with the UI from the thread, and it raises exceptions. The solution is not to clog the main thread with the XojoScript, it is to stop accessing the UI from it. That means rewriting all the methods that access the UI, or using a timer to call them. Sure, it is not as simple as throwing an App.Doevents in, but on the long run, you will gain in stability and peace of mind.

There’s not much I can add that Michel and Bob have not already covered. It is never acceptable to use DoEvents in a desktop application. It shouldn’t even be callable, but it is because HTTPSocket uses it in synchronous mode. (I suspect this is also why Xojo.Net.HTTPSocket has no synchronous mode.)

The only solution is to break the bad habits and refactor your code. The XojoScript context methods interacting with the UI is the problem, and you’ll need to change that. Those context methods should probably only interact with the thread which calls the script. I can’t tell you exactly how to refactor it, as there are a number of options, but it must be done. There’s literally no way around it.

Also, it’s hilarious that esoTalk turned that class name into a link…

Thom-

While I agree with everything you have said, I still dont know how to do it. But accessing context methods that interact with the GUI is an essential part of my xojoscripts. Without that capability, I would have nothing. My program allows the user to write scripts to do numerical methods and molecular orbital calculations, and plot the results. Hard to do this without accessing the GUI. My program, mathscriptor (www.mathscriptor.org), is used at over 60 universities in 12 countries. It is free. But it needs help for all the reasons previously outlined.

I agree that getting rid of app.doevents is a critical goal, and I appreciate your time and advice in helping me figure out how to do this.

I have learned a lot from the above discussions. But I do not understand why it is OK for a thread to access a timer that manipulates the UI, but not OK for a thread to call a context method to manipulate the UI. Both the timer and the context method are operating in the main loop.

Based on Bob Keeney’s comments, my best solution is to move my xojoscript into a thread, and interact with the UI by using timers, and nothing but timers. That is indeed possible, but will take months of programming.

Finally, xojoscript includes both an Input and a Print statement. Both interact with the UI. Seems to me using either of these statements inside a xojoscript running in a thread will also throw a ThreadAccessingUIException. What am I missing?

[quote=180969:@Robert Birge]Dear Thom,

I only use the paint event of the canvas to update the canvas. What I have tried to do is create a thread which calls the timer. Then I try and update the gui by replacing my calls to app.doevents (which work fine in carbon) with app.YieldToNextThread, which does indeed run the thread. The Thread then starts the timer. The timer fires and calls canvas.invalidate. But the paint event does not fire until the xojoscript is done. That is the problem.

If you have any suggestions, I would welcome them. Thanks for your help.[/quote]

Hmm… Maybe have the timer call Refresh rather than Invalidate on the canvas? If I recall correctly, that forces an immediate redraw, rather than waiting until the OS thinks it has time…

You are correct that the timer executed in the main loop, but the context methods do not. They execute in the thread that called XojoScript.Run.

Input and Print do not themselves interact with the UI. They will execute exactly as a context method, which means they can execute in a thread.

Timer.Action will always execute on the main thread, no matter what. That is what makes them useful in this problem. You use them to essentially say “ok, next main event loop, do this work.” What your code is doing now says “do this work before you execute any additional code.”

Unfortunately, without seeing code, I can’t give you any direct advice. But something will need to be done.

No, it is extremely rare to ever need to do a Refresh instead of Invalidate. Mouse operations come to mind, but that’s about it. In this case, Refresh vs Invalidate will not help the problem, and Refresh increases the likelyhood of wasted cpu time.

If you mean, why doesn’t this work instead of DoEvents, it’s because the context method monopolizes the main UI thread and prevents the timer from ever firing, and the UI from refreshing, or any other normal events for that matter. By moving the xojoscript, and therefore the context methods, into a thread, you allow the main UI thread to get time, so the timer fires and the UI refreshes.

Tim,

Now I understand why my application will only be stable if I put my xojoscript run event into a thread. I will now have to figure out how to get all those context methods that involve the UI to manipulate the UI via a timer.

This will require a major refactoring of my code, but it needs to be done.

This would seem to suggest that you are populating a dataset that the canvas paint event renders. If that is the case, you may not have as much interaction with the UI as you think. Your threaded context methods can interact directly with data, even window properties, as much as they like. They just can’t access any property that interacts with the display. So basically, any property you added to a window or control subclass is fair game for the thread. The standard properties are pretty much off limits. So if your code is something like Load a bunch of values into an array and then refresh the canvas, only that last bit needs a timer. On the other hand, if you’re toggling the visibility of a bunch of controls, then that needs to be refactored.