OfferBreak, RunChildSteps with CanecllationToken and TapThread

Whenever a TestPlan is aborted a nasty looking error message appears that draws deep groans from the operators. I’ve ignored it until now, but I suppose I should really do something about it.
I have many parent steps that use RunChildSteps, OfferBreak exists in just about every possible long-running loop I’ve ever created and I have used TapThread.Sleep() in a few place,

  1. How can a CancellationToken be used with RunChildSteps?
  2. How is an abort to be handled gracefully?
  3. How can CancellationTokens be used with UserInput.Request(someObj)?
  4. Can a CancellationToke be used instead of TapThread.Sleep(), i.e. var cancellationTriggered = new CancellationToken().WaitHandle.WaitOne(200) ?
1 Like

The thing you’ll have to remember is that there’s nothing particularly special about the AbortToken in OpenTAP - it’s just a CancellationToken. So the same principles that apply to all CancellationTokens apply to AbortToken.

  1. The AbortToken can be accessed in any TestStep using StepRun.StepThread.AbortToken. There’s no need to pass it to child steps or anything. Whatever handling you do, do it in all your steps, if there’s a long-running process.
  2. I’m wondering if you’re using either Thread.Sleep (don’t do that) or TapThread.Sleep. If you’re using TapThread.Sleep, you should always surround it with a try/catch, because if the plan is Aborted while this is “sleeping”, it will throw an exception. So you’ll need to surround these with a try/catch to handle it cleanly. If you have a long-running process, any long-running process should have the capability of cancellation, whether synchronous or async. You may need to change your code that calls your long-running loop to pass in a CancellationToken argument, and pass in the AbortToken. Again, surround this call with a try/catch. If your long-running loop is in your TestStep code, we may need some more details to figure out why it’s throwing an error, because abort should be handled cleanly straight from a TestStep. If it’s leaving your instruments or DUTs in a bad state, then maybe implement a PostPlanRun to fix that.
  3. Is your UserInput meant to be a GUI? You can do a couple things. If you want to handle it cleanly from your UserInput, you can pass the AbortToken as a property or argument to the constructor. You could also implement your own CancellationToken to be triggered from a button on the GUI. Remember to run long-running cancellable Tasks asynchronously from a GUI, including the UserInput object.
  4. Why would you do that?
2 Likes

This was because of the exception you mentioned in point 2 when using TapThread.
3. A nice async gui example would be nice. I hate gui’s, so I try to avoid them whenever possible.

1 Like

This is a function from an OpenTAP plugin for a yet-unreleased Keysight product (this plugin will be open source when the product is released). It’s in just a normal class, and this method represents a button in the GUI when used as UserInput.Request.

        [Display("Run Selected Standard", Order: 4.1)]
        [Layout(LayoutMode.Normal)]
        [EnabledIf(nameof(IsRunning), false, HideIfDisabled = true)]
        [Browsable(true)]
        public async void RunSelectedStandard()
        {
            if (IsRunning)
                return;
            try
            {
                IsRunning = true;
                OnPropertyChanged(nameof(StandardProgressStr));
                await Task.Run(() => NtsaApiRef.Api.RunStepsforStandardAsync(SelectedStandard, _cancelSource.Token));
                if (!_measuredStandards.Contains(SelectedStandard))
                    _measuredStandards.Add(SelectedStandard);
                OnPropertyChanged(nameof(AllStandardsMeasured));
                OnPropertyChanged(nameof(StandardsMeasuredProgress));
            }
            catch (OperationCanceledException)
            {
                _cancelSource = new();
            }
            catch (Exception e)
            {
                if (ParentSource != null)
                    Log.Error(ParentSource, e);
            }
            finally
            {
                IsRunning = false;
                StandardProgress = 0;
            }
        }

If you want a more general example, I’ll have to take some time to make it and publish to GitLab.

As for 4, would handling the exception it throws solve the problem? Doing something complicated with the CancellationToken sounds like not the best practice.

1 Like

_ = new CancellationToken().WaitHandle.WaitOne(200) ; works exactly the same as TapThread.Sleep(200) ; but because it isn’t attached to TapThread it doesn’t need a try/catch and isn’t aborted. This was handy for very small but necessary delays. A quick n dirty way but as you say, probably not best practice.
For other instances, I’ve used PlanRun.MainThread.AbortToken.WaitHandle.WaitOne(5000); for longer delays. This was nice for typical long communications delays. I suppose it’s not a big ask to put a try / catch around TapThread though.

Thanks for the example and I’m looking forward to Keysight’s open-source stuff.

1 Like

Okay I see. So I’d say using TapThread.Sleep surrounded by a try/catch is going to be your best bet to not only catch these errors in the future but also make sure you are aborting cleanly, so that the instrument or DUT is not left in a weird state, or instrument handles left open, etc.

No problem! Let me know if you are writing something and get stuck, I can make a real example to put on GitLab.

I look forward to Keysight’s open-source stuff too - I hope we do more.

1 Like