Thread replacement for DoEvents

I’m trying to update a chart with data generated by a timer event, while waiting for a specified number of seconds, but the timer is not firing while my method DoHold below is is running. I can make it work using DoEvents in place of the thread.

Dim d as New date
Holding=True
DoHold(EndTime) ’ During this hold I’m waiting for until EndTime (D.Totalseconds + a constant) is reached

Sub DoHold(EndTime) ’ The timer is not firing while this code is running, but it does if this is replaced with a DoEvents loop. What is disabling the timer?
Holding=True
ThreadHold.Run
While Holding
Wend
ThreadHold.Kill

ThreadHold.Run
While Holding
Dim D as new date
holding = (HoldEnd > D.TotalSeconds)
App.YieldToNextThread
Wend
Holding=False

Xojo is single-threaded, which means that only one thing is running at once (be it thread or non-thread). It’s fully cooperative.

However, there is a difference between Threads and Events.

Threads can fully share time and work with each other.
Events: Only one event at a time can run, and until it exits, all other events are blocked.

In your example, I understand that you have a timer calling DoHold. It launches a thread (good!) but it then does a While/Wend loop until the thread is done (bad!). This will block all other Events.

Refactor it like this:

Sub AllDone
   msgBox "The thing has finished!"
End Sub


Sub DoHold(EndTime)
  ThreadHold.Run
End Sub


ThreadHold.Run
   While true
     me.sleep(1000) ' change this to a reasonable number depending on how precise you need it to be
     Dim D as new date
         if  (HoldEnd > D.TotalSeconds) then
           exit While ' exit the loop, which will terminate the thread.
        end if
    Wend
  Call AllDone()   ' finished!

The trick is to use event-driven programming: rather than the parent waiting on the child to finish, the child itself notifies the parent when it is done.

Also - if the completion routine needs to do UI stuff, it can’t be called by the thread directly.

In that case, you need to call the AllDone() call from a Timer, like this:

ThreadHold.Run
   While true
     me.sleep(1000) ' change this to a reasonable number depending on how precise you need it to be
     Dim D as new date
         if  (HoldEnd > D.TotalSeconds) then
           exit While ' exit the loop, which will terminate the thread.
        end if
    Wend
  myTimer = new Timer
  Timer.period = 0 ' immediately
  Timer.mode = 1 ' call once
  AddHandler myTimer.Action, AddressOf AllDone

See http://developer.xojo.com/addhandler

As Michael mentioned, there’s probably a better solution with event driven design than this locking loop within a thread. I’m curious and interested in offering a solution, but I’d like to understand what the goal is. Currently I have no idea what we’re really trying to do between the timer, thread, and chart.

Is the chart only to update every X seconds?
How/why is a timer generating data?
How does that relate to the thread?

I find the best way to convey your goal is to describe exactly what you want to do, rather than the technologies you think you need to use.

Thanks, now I understand why my first attempt at threading failed. I simplified my code for the example, and left out the data collection/plotting code that is triggered by the timer. The purpose of the program is to run experiments to find the best protocol that involves charging and discharging a capacitor. I want to record voltage and current every second during both voltage holds and voltage ramps. The timer code collects and plots the data, and increments the voltage during ramps. I’m not familiar with addhandler, so I’ll look into that. I’ve used DoEvents for code like this for years with no problems I’ve noticed, but figured it was about time to learn about threading.

I don’t think I’ve ever seen that stated before; very helpful, thanks!

All I really need is a delay. I thought about using a sleeping thread like Michael suggested, but I understand threads are interruptible, so timing might be erratic. I decided to create a delay method using microseconds, which I pass the delay in seconds (secs) that I want:
Dim EndT as Double=Microseconds+secs*1000000
Do
Loop until microseconds>EndT

This works, but my data plot did not display until the whole protocol method completes, not with each data point. I’m using a Chartdirector Plot, and neither me.refresh, or me.invalidate forces a redraw. I had to use a timer to call the plot routine, rather do the plotting call from the protocol method.

Oops, I was over optimistic, forgetting that the timer events won’t happen during my protocol method. I have given up for now and put an app.doevents in my delay loop, so I at least have something I can work with.

Not sure what you mean by threads being “interruptible”. Interrupted by what? I’ve never had thread.sleep timing behave erratically. If WakeEarly is False (its default value) the thread will sleep for the entire time specified.

[quote=373040:@David Graham]Dim EndT as Double=Microseconds+secs*1000000
Do
Loop until microseconds>EndT[/quote]

This will burn a lot of CPU for nothing. You would obtain the very same result with a timer, and place all the code after your tight loop in the Action event of the timer.