Question

Is there a way to get multiple async methods (tasks or threads) to wait until certain individual conditions happen?

I am trying to get a call/response system working with an arduino controlling a mechanism via the serial port.

The controlling computer creates a Task/thread that handles each job assigned to the arduino asynchronously (home the mechanism, set position, move, etc.) The arduino reports back with how it is doing on each job ("successfully homed", "moved to position", etc.) which allows the job to continue along its code path.

However, everything I've tried has some issue with it, and so I'm looking for some advice on how to proceed.

Here are things I've tried. (Feel free to ask specifics about these.)

  • while(!doneWaiting){thread.sleep(5);} loops when I was using a thread-based system instead of a task based system. This ate up the entire CPU when I had multiple threads waiting for a response from the arduino (for example, I was trying to moveAxis1 and moveAxis4 at the same time, and both were waiting for permission to proceed, crashing the program due to over-use of memory.)

  • Repeatedly parsing the SerialPort input for the string that signified for that job to proceed. Also ate up memory and would occasionally ignore the "OK to proceed with job" response.

  • Eventually I implemented the serialPort.DataReceived event and used ManualResetEvents.WaitOne(). When new data was found in the SerialPort, it would ManualResetEvents.Set()and allow the code to flow past WaitOne(). This would be the best option, but it triggers ALL waiting threads as opposed to one particular waiting thread at a time. I need some threads to wait for their personal response while others are allowed to continue.

Here is my current implementation, which is nonfunctional.

class Program
{
    public static SerialPort serialPort = new SerialPort("COM9", 9600, Parity.None, 8, StopBits.One);
    public static string newDataContent = string.Empty;
    public static bool homed = false;
    static TaskCompletionSource<bool> homeRelease = new TaskCompletionSource<bool>(); //this is the important bit

    static void Main(string args[])
    {
    serialPort.DtrEnable = true;
    serialPort.DataReceived += new SerialPortDataReceivedEventHandler(serialPort_ReadArduinoData);

    serialPort.Open();

    //...

    char cmdType = Console.ReadLine().ToLower().ToCharArray()[0];
    switch(cmdType)
        {
        case 'h':
            
            Task.Run(Home);
     
        break;
        
        //...the rest of the commands...

        }
    }
}

Here is the what the Home task looks like. It is paused in the middle with `homeRelease.Task;` and awaits its completion.

static async Task Home()
    {
        Console.WriteLine("Running Task Home()");
        WriteMyCommand(2, 0, serialPort); //this function is proven working. Writes the command to the arduino.
        Console.WriteLine("awaiting homeRelease...");
        await homeRelease.Task;
        Console.WriteLine("...homeRelease Received");
        homed = true;
        homeRelease.SetResult(false);
    }

The serialData_ReadArduinoData event looks like this:

public static void serialPort_ReadArduinoData(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort spL = (SerialPort)sender;
    byte[] buf = new byte[spL.BytesToRead];
    spL.Read(buf,0,buf.Length);
    newDataContent = $"{System.Text.Encoding.ASCII.GetString(buf)}";

    Console.WriteLine($"Received: {newDataContent}");

    switch(newDataContent[1])
    {
        case '2': //response Home command
            Console.WriteLine("Detected home response");
            homeRelease.SetResult(true);
            break;
        //rest of switch statement
    }
}

When I do one home command, the system works as expected, and pauses the Home task at the await until the "arduino has homed" signal is complete. However, when I request another instance of the same job, I get the error, 'An attempt was made to transition a task to a final state when it had already completed.'

Is there a way I could get multiple of these threads/tasks/jobs to wait for an individual flag raised by the switch statement in serialPort_ReadArduinoData that allows them to continue working? Or is this approach just completely deranged?

I am totally stumped as to where I could go next. Any assistance would be very much appreciated: I am over deadline and also not a professional programmer.

 4  67  4
1 Jan 1970

Solution

 2

Your specific issue with error "An attempt was made to transition a task to a final state when it had already completed." seems to stem from the fact that homeRelease is a static TaskCompletionSource<bool>, and seems to always refer to the same object. As it is at some point already set to true (or false), interacting with it again will cause your error, as it is not meant to be set more than once.

Note that Task.Run returns a Task object which you can track - and contains properties you might consider useful, such as Task.IsCompletedSuccessfully.

2024-07-10
Pedro Costa