Asynchronous tasks are very common when running special or long tasks that may negatively impact the overall operation
Standard C# language provides multiple ways to run asynchronous operations (such as the async modifier for some methods), these can be used in FT Optix as long as those methods does not access any of the project structure (pages, tags, etc).
- Example:
- async method to read/write a CSV -> GOOD
- async method to read a CSV and import it into the TranslationDictionary -> BAD
These asynchronous tasks are InformationModel-safe and can be used to access project nodes without concerns
This kind of task allows a specific method to be run every x milliseconds, for example to blink a LED or to handle a watchdog.
Please note: the specified interval, is actually the delay between two consecutive runs of the method, and the operation is impacted by the length of the instruction to process. The next run is delayed until the task is completed, for example:
Having a task:
- PeriodicTask declared every 50mS
- Method run by the PeriodicTask takes 100mS to process
With this timings, the task is actually run every 150mS, as the interval is actually the delay after which the task is run again.
public override void Start()
{
// Creates a task that runs every second without impacting the UI
myPeriodicTask = new PeriodicTask(IncrementVariable, 1000, LogicObject);
myPeriodicTask.Start();
}
public override void Stop()
{
// When the NetLogic is disposed, kill the task too
myPeriodicTask?.Dispose();
}
private void IncrementVariable()
{
// Action to be run every tick of the periodic task
…
}
private PeriodicTask myPeriodicTask;This kind of task will trigger a method after x milliseconds it is called, this can be useful for example to hide a toast notification after some time
public override void Start()
{
// Create an action that is run after 10s the page is loaded
// this could also be a method called by a button or any trigger
myDelayedTask = new DelayedTask(ResetLabelText, 10000, LogicObject);
myDelayedTask.Start();
}
public override void Stop()
{
// Make sure to dispose the task when the NetLogic is stopped
myDelayedTask?.Dispose();
}
private void ResetLabelText()
{
// Action to be run after the delay
…
}
private DelayedTask myDelayedTask;This is the most similar approach to the async modifier of the standard C# scripting, it allows to create a dedicated thread and run it without affecting the main code operation.
An async modifier can also be used, but an async method cannot access any project node (it may cause concurrency issues), in such cases, use a LongRunningTask instead.
class public override void Start()
{
// Create a dedicated thread to run a long or demanding process
// Without stopping the FT Optix Runtime or Studio
myLongRunningTask = new LongRunningTask(ProcessCSVFile, LogicObject);
myLongRunningTask.Start();
}
public override void Stop()
{
// Make sure to dispose the thread once the NetLogic is stopped
myLongRunningTask?.Dispose();
}
private void ProcessCsvFile(LongRunningTask task)
{
// Process that takes long time to be run
…
}
private LongRunningTask myLongRunningTask;An overload of the LongRunningTask constructor accepts a task and an object, the task can be used to check if a cancellation was requested, while the object can be used to pass arguments to the thread that is being generated
[ExportMethod]
public void StartExam(bool free, string clientID = "", string examCode = "")
{
Log.Debug(examCode == "" ? "ExamLogic.StartExam" : "ExamLogic.StartExamCertification", "Starting exam");
startExamTask?.Dispose();
var argumentsObject = new StartExamParameters { isFree = free, clientID = clientID, examCode = examCode };
startExamTask = new LongRunningTask(StartExamMethod, argumentsObject, LogicObject);
startExamTask.Start();
Log.Debug(examCode == "" ? "ExamLogic.StartExam" : "ExamLogic.StartExamCertification", "Leaving method");
}
private void StartExamMethod(LongRunningTask task, object examParameters)
{
// Get the parameters
var argumentsObject = (StartExamParameters)examParameters;
string clientID = argumentsObject.clientID;
string examCode = argumentsObject.examCode;
bool free = argumentsObject.isFree;
// Do other stuff
// ...
}
private sealed class StartExamParameters
{
public bool isFree;
public string clientID;
public string examCode;
}
private LongRunningTask startExamTask = null;When a task is called asynchronously (especially in LongRunningTask), then the method itself should check if a cancellation request is pending (see documentation here) and act accordingly, for example:
public class LongRunningLogic : BaseNetLogic
{
public override void Start()
{
// Create the new LongRunningTask and run it
myTask = new LongRunningTask(MyMethod, LogicObject);
myTask.Start();
}
public override void Stop()
{
// Request a cancellation event to the async task
myTask?.Dispose();
}
public void MyMethod()
{
for (int i = 0; i < 1000; i++)
{
// Check if the somebody requested to dispose this task
if (myTask.IsCancellationRequested)
return;
// Do some work here
Thread.Sleep(1000);
Owner.Get<Led>("LED1").Active = !Owner.Get<Led>("LED1").Active;
}
}
private LongRunningTask myTask;
}Warning
This is a non-public API (accessed using C# reflections) and may be subject to changes, please refer to the official documentation to access the publicly available APIs which are guaranteed to be maintained.
[ExportMethod]
public void DoStuff()
{
// Initialize the LongRunningTask
var myTask = new LongRunningTask(MyMethod, LogicObject);
// Do some stuff
// Check if the task is still running
while (IsTaskRunning(task))
{
Log.Info("The task is still running...");
Thread.Sleep(100);
}
}
private void MyMethod(LongRunningTask task)
{
// Do some work
throw new NotImplementedException();
}
private static bool IsTaskRunning(LongRunningTask task)
{
// Get the type of the LongRunningTask
Type taskType = task.GetType();
// Get the FieldInfo object for the isRunning field
FieldInfo isRunningField = taskType.GetField("isRunning", BindingFlags.NonPublic | BindingFlags.Instance);
// Check if the field was found
if (isRunningField != null)
{
// Get the value of the isRunning field
bool isRunning = (bool) isRunningField.GetValue(task);
return isRunning;
}
else
{
throw new InvalidOperationException("The isRunning field was not found on the LongRunningTask type.");
}
}