Search code examples
c#asynchronousserial-portcancellation

Is this way of using Task cancellation before closing port correct?


I have a C# GUI application where when one clicks on a button and then MyMethod starts running async way and it continuously(inside a do while loop) calls MyTask. MyTask writes and reads data from a port. And these data is passed to MyProgressMethod for further proccess.

I want to implement a button which would first cancel/stop MyTask and then close the port.

Being new with this async way, I relied on some online examples which I stumbled upon and the rest were a difficult to grasp. Based on what I read, I came up with the following to achieve cancellation with a button. But I don't quite understand the mechanism and wondering whether the following way is correct:

Declaring a CancellationTokenSource object at the very beginning of class:

CancellationTokenSource my_cancelationTokenSource = null;

The button click event. Button event calls MyMethod:

 private void Button_Click(object sender, RoutedEventArgs e)
 {
    //Some code
    MyMethod();
 }

MyMethod calls MyTask each second and passes data to MyProgressMethod:

private async void MyMethod()
{

    my_cancelationTokenSource = new CancellationTokenSource();

    do
        {
            await Task.Delay(1000);
            //Process some code
            byte[] my_received_data = await MyTask(my_sent_data, my_cancelationTokenSource.Token);
            MyProgressMethod(my_received_data, my_sent_data);
        }
        while (true);
}

MyTask read and writes to the port(Needs to be cenceledbefore the port is closed):

private async Task<byte[]> MyTask(byte[] my_sent_data, CancellationToken cancelToken)
{

    await Task.Delay(200, cancelToken);//??? What should happen here?
    //Some code
    
}

Button event for canceling task and then closing the port:

private  void Button_Disconnect_Click(object sender, RoutedEventArgs e)
{

    my_cancelationTokenSource.Cancel();

    if (my_port.IsOpen)
    {
        my_port.Close();
    }

}

How can this code be optimized for stability?(i.e. port should only be closed after task is cancelled)


Solution

  • The solution here is to not close the port directly from the Disconnect button. Instead, cancel the token, and catch OperationCanceledException in MyMethod:

    private CancellationTokenSource my_cancelationTokenSource;
    
    private async void MyMethod()
    {
        my_cancelationTokenSource = new CancellationTokenSource();
        try
        {
            while (true)
            {
                await Task.Delay(1000, my_cancelationTokenSource.Token);
                //Process some code
                byte[] my_received_data = await MyTask(my_sent_data, my_cancelationTokenSource.Token);
                MyProgressMethod(my_received_data, my_sent_data);
            }
        }
        catch (OperationCanceledException)
        {
            try
            {
                my_cancelationTokenSource.Dispose();
                my_cancelationTokenSource = null;
                my_port.Dispose();
            }
            catch {  }
        }
    }
    
    private void Button_Disconnect_Click(object sender, RoutedEventArgs e)
    {
        my_cancelationTokenSource?.Cancel();
    }
    

    Notes:

    • my_cancelationTokenSource becomes a field rather than a local variable.
    • Pass the token to the Task.Delay functions also. (It's unclear why you need the delays, normally you just wait for a response on the port).
    • I don't know what exactly you want done on cancellation, I'll leave that to you.
    • try/catch the closure of the port, which you should do via Dispose, just in case it throws.