Again Threads and UI-tasks

I know that UI tasks from a thread are bad and not supported. Using Xojo it is enforced.

Still I see the need for some UI support similar to performSelectorOnMainThread in Objective-C.

For example an application which needs to print several jobs. The job creation is performed in a thread but the print job title needs to be assigned via the window title.

It would be pretty easy to code everything in a thread and then to call something like

[quote]start performOnMainThread
self.title =foo"
end performOnMainThread [/quote]

Even when there is a speed penalty.

Comments please. Is there already a feature request?

Cheers,

Tom

Not sure if there is a feature request, and my knowledge of threads is at a NOOB level.

I’m sure there is a good reason for not allowing UI access from threads, but the ability to access UI from threads would be a huge benefit.

The reason they went down the road of throwing exceptions for accessing UI outside the main thread is that doing so is a BAD idea that has unpredictable consequences. They’ve been telling us it’s a bad idea for a couple of years, and our response has often been to negotiate rather than listen. So to repeat, it’s a bad idea. Platform UI frameworks aren’t all thread safe.

The standard way of safely manipulating UI from a thread is to move the UI manipulation to a Timer. In the thread, set period of Timer to 1, mode to 1. In thread, wait for the Timer to complete its action. Keep some flag in a Timer subclass to indicate that.

-Brad

I know that Thread vs. UI stuff. This is not the question and the request is not to change that.

The request is to add a feature to perform a call from a thread which is performed on the main thread (see Apples Doc for performSelectorOnMainThread).

My example above makes it really hard to use a thread and a timer properly. And therefore I request a similar feature for Xojo.

It looks like a pretty straightforward Timer implementation to me. I’ve done many just like it. 5 minutes versus waiting a few release cycles for that to be implemented, if it’s worth implementing…

-Brad

[quote=6891:@Thomas Eckert]I know that Thread vs. UI stuff. This is not the question and the request is not to change that.

The request is to add a feature to perform a call from a thread which is performed on the main thread (see Apples Doc for performSelectorOnMainThread).

My example above makes it really hard to use a thread and a timer properly. And therefore I request a similar feature for Xojo.[/quote]

Been there done that caused havoc - removed

I have been writing ThreadSafe versions of the following controls:

  • PushButton
  • ProgressBar
  • Window
  • Label

It has been working out very nicely.

The basic outline:

  • Make a subclass, e.g. “cLabel”
  • make internal properties for all values, e.g. “private mCaption as string” which will shadow the actual ones
  • make computed properties to shadow the superclass ones. The Getters are trivial. The Setter need to fire a timer so the update happens later:
cLabel subclass:

  Caption.Get as string
      return mCaption
  Caption.Set(value as string)
      mCapiton = value
      UIUpdateRequest

UIUpdateRequest
    if mTimer = nil then
       mTimer=new Timer
       AddHandler mTimer.Action me.UpdateUICallback
       mTimer.period = 10 // (for performance reasons, you may want to make this longer or shorter)
     end if
  mTimer.Mode=Timer.ModeSingle

UPdateUICallback
   if me.Caption <> mCaption then
      me.Caption = mCaption
  end if
  etc...

You can extend this technique for all the properties you want.

It’s really quite easy. So easy, in fact, that I think REAL should just modify their existing Framework to do this internally. I believe I submitted a Feature Request for this some time ago, but it was closed.

Yes. You’re right. Unfortunately is it a more tricky for my example.
A thread runs and creates a print job. Unfortunately the print job title (which is very important for me) has to be assigned via the Window title. So I can’t run async here.
Of course I know how to solve this but it is a larger re-write.

It was so easy to use just a thread. And it would easy for us via a “performOnMainThread” feature…

@ Norman:

2nd service

[quote=6906:@Michael Diehr]I have been writing ThreadSafe versions of the following controls:

  • PushButton
  • ProgressBar
  • Window
  • Label

It has been working out very nicely.

The basic outline:

  • Make a subclass, e.g. “cLabel”
  • make internal properties for all values, e.g. “private mCaption as string” which will shadow the actual ones
  • make computed properties to shadow the superclass ones. The Getters are trivial. The Setter need to fire a timer so the update happens later:

You can extend this technique for all the properties you want.

It’s really quite easy. So easy, in fact, that I think REAL should just modify their existing Framework to do this internally. I believe I submitted a Feature Request for this some time ago, but it was closed.[/quote]

I have no doubt your FR was closed: shadowing properties is a bad thing!
So you are preventing to do a bad thing doing another bad thing. :wink:

Thomas, and Massimo : I believe you are both wrong - this is in-essence a “write-back” cache. Since the Getter always returns the (most recent) cached value, you can always safely get the value, even in a thread.

Thomas, in your case you would do this:

cWindow Title.Get as string return mTitle Caption.Set(value as string) mTitle = value UIUpdateRequest

Massimo: can you explain why shadowing a property is a bad thing?

This would most likely work, but has a few costs:

  • potentially being out of sync with the underlying control (for example, declares)
  • extra memory usage
  • time needed to update every place in the framework

While I’m not Massimo, I can explain this with a simple snippet of code:

dim ctrl as cLabel = new cLabel dim ctrl2 as Label = ctrl ctrl2.caption = "abc"

The last line actually ends up invoking Label’s Caption setter and not cLabel’s setter.

[quote=6917:@Joe Ranieri]This would most likely work, but has a few costs:

  • potentially being out of sync with the underlying control (for example, declares)
    [/quote]

True, but if you are using declares, one could argue “for expert use only: know what you are doing”

a few bytes, probably quite trivial.

[quote=6917:@Joe Ranieri]* time needed to update every place in the framework
[/quote]

The class I’m building is basically a drop-in replacement, so it may be as simple as search for “Window” and replace it with “cWindow”.

The question is : is it easier to do that, or to do the other refactoring of your threaded code. Every case is probably unique.

[quote=6917:@Joe Ranieri]
While I’m not Massimo, I can explain this with a simple snippet of code:

dim ctrl as cLabel = new cLabel dim ctrl2 as Label = ctrl ctrl2.caption = "abc"

The last line actually ends up invoking Label’s Caption setter and not cLabel’s setter.[/quote]

Good point: that’s a potential “gotcha”.

It would be nice if the language / framework had the option to fully virtualize computed properties.

For strings, it’s all of the bytes for the string and then some overhead for reference counting. While small for a label, it could get quite large for a TextArea (and it would need to be copied every time the TextArea’s text changed).

Note that I didn’t suggest this technique for TextArea :slight_smile: Clearly, the complexity of the underlying control matters. It seems to me a gracious compromise could be reached, something like :

The following controls are known to be thread-safe:

  • PushButton
  • Label
  • ProgressBar
  • etc.

The following controls are known to be thread-UN-safe

  • TextArea
  • etc.

In my experience, having a thread-safe ProgressBar, Label, and PushButton are terribly convenient, it allows me to write code like this:

Thread1.Run ' adjust the UI PushButtonStop.visible = true PushButtonRun.visible = false ' do the task for i = 1 to nmax progressBar1.value = i label1.caption = "Processing " + str(i) + " of " + str(nmax) foobar() next ' adjust the UI label1.caption = "All done" PushButtonStop.visible = false PushButtonRun.visible = true

Sure, one can do this other ways, but the code then requires multiple timers and a state machine - OR the elimination of threads altogether.

This strikes me as a good third party library opportunity rather than something that should take more space in the frameworks and cloud the party line on thread safety.

Perhaps it could be a part of one of the open-source code repositories such as https://github.com/macoslib ?

I agree. A library or plugin could better handle this.
However, I think Xojo Inc. should try to offer an official workaround to this problem. Too many people need to access UI from threads and, in lack of an official library/plugin/mechanism every one is coming with his own solution which, sometimes, is worst than the problem it solves.

I’d appreciate if Xojo will provide a library or plugin for this, even if not officially supported. At least there would be one official solution.

[quote=6917:@Joe Ranieri]
While I’m not Massimo, I can explain this with a simple snippet of code:

dim ctrl as cLabel = new cLabel dim ctrl2 as Label = ctrl ctrl2.caption = "abc"

The last line actually ends up invoking Label’s Caption setter and not cLabel’s setter.[/quote]

Thanks Joe, I couldn’t explain better.

[quote=6917:@Joe Ranieri]While I’m not Massimo, I can explain this with a simple snippet of code:

dim ctrl as cLabel = new cLabel
dim ctrl2 as Label = ctrl
ctrl2.caption = “abc”

The last line actually ends up invoking Label’s Caption setter and not cLabel’s setter.

[/quote]

But I would expect this, since you used Label instead of cLabel. Why would this be a “gotcha”? Just like most programming, you need to understand the tools to use them properly.

Then just suppose you pass the cLabel instance to a class which expect a Label. This class will handle your cLabel as a Label.