Thread replacement for DoEvents

  1. 8 months ago

    David G

    9 Feb 2018 Pre-Release Testers, Xojo Pro Phoenix, AZ

    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

  2. Michael D

    9 Feb 2018 Pre-Release Testers, Xojo Pro
    Edited 8 months ago

    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.

  3. Michael D

    9 Feb 2018 Pre-Release Testers, Xojo Pro
    Edited 8 months ago

    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

  4. Tim P

    9 Feb 2018 Pre-Release Testers feedback://46303,51561

    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.

  5. David G

    10 Feb 2018 Pre-Release Testers, Xojo Pro Phoenix, AZ

    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.

  6. Julia T

    10 Feb 2018 Sandy Hook, Connecticut
    Edited 8 months ago

    @Michael D Only one event at a time can run, and until it exits, all other events are blocked.

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

  7. David G

    10 Feb 2018 Pre-Release Testers, Xojo Pro Phoenix, AZ

    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.

  8. David G

    10 Feb 2018 Pre-Release Testers, Xojo Pro Phoenix, AZ

    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.

  9. Julia T

    11 Feb 2018 Sandy Hook, Connecticut

    @David G 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.

    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.

  10. Michel B

    11 Feb 2018 Pre-Release Testers RubberViews.com
    Edited 8 months ago

    @David G Dim EndT as Double=Microseconds+secs*1000000
    Do
    Loop until microseconds>EndT

    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.

or Sign Up to reply!