I'm using C# in Visual Studio 2019. I seem not to be able to correctly run and cancel a task. I'm sure I am missing the point, but reading a lot of articles only creates more questions.
EDIT --> For a shorter and easier to understand example, see further below.
I am using a SerialPort and want it to continuously read data. My class COMDevice has some public properties that I can use to test with.
public Task _RxTask;
public CancellationTokenSource _ctsRxTask = new CancellationTokenSource();
public CancellationToken _ctRxTask;
After the initialization process, I start a async method that continuously reads the SerialPort.
public bool Initialize()
{
... Do some initialization stuff
_ctRxTask = _ctsRxTask.Token;
_RxTask = Task.Run(() => ReceiveCommandsAsync(_ctRxTask));
Debug.WriteLine($"Initialize _RxTask.Status = {_RxTask.Status}");
}
The ReceiveCommandsAsync method is shown below. Don't bother too much with the processing of the received data (it basically checks if the sequence "A\n" or "[Command]\n" is received).
private async Task ReceiveCommandsAsync(CancellationToken ct)
{
if (ct.IsCancellationRequested)
{
Debug.WriteLine("ct.IsCancellationRequested 1");
return;
}
var buffer = new byte[1024];
StringBuilder cmdReceived = new StringBuilder("", 256);
try
{
while (_serialPort.IsOpen)
{
Debug.WriteLine($"ReceiveCommandAsync 1 _RxTask.Status = {_RxTask.Status}");
int cnt = await _serialPort.BaseStream.ReadAsync(buffer, 0, 1024).ConfigureAwait(true);
Debug.WriteLine($"ReceiveCommandAsync 2 _RxTask.Status = {_RxTask.Status}");
cntMax = Math.Max(cnt, cntMax);
for (int i = 0; i < cnt; i++)
{
if ((char)buffer[i] == '\n')
{
if ((cmdReceived.Length == 1) && (cmdReceived[0] == 'A'))
mreAck.Set();
else if (cmdReceived.Length != 0)
{
// Check if command matches
string s = cmdReceived.ToString().Substring(4);
if (!_RegisteredCmds.Contains(s))
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} ReceiveCommandsAsync: cmd {cmdReceived} not found");
}
cmdReceived.Clear();
}
else
cmdReceived.Append((char)buffer[i]);
}
if (ct.IsCancellationRequested)
{
Debug.WriteLine("ct.IsCancellationRequested 2");
return;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} ReceiveCommandsAsync: Exception {ex}");
}
}
There are a few issues:
First, the Debug.WriteLines showing the Task.Status are continuously showing "WaitingForActivation". But my task is running, because when my connected device sends data, the data is received. Shouldn't I see "Running" instead?
Second, in a test form, I'm closing the COMDevice with below code:
private async void btnClose_ClickAsync(object sender, EventArgs e)
{
_COMDevice?._ctsRxTask.Cancel();
await Task.Delay(1000);
_COMDevice?.Close();
_COMDevice = null;
}
But ct.IsCancellationRequested in my ReceiveCommandsAsync is never triggered. In the contrary, I seem to receive an Exception:
20:12:02:446 ReceiveCommandsAsync: Exception System.IO.IOException: The I/O operation has been aborted because of either a thread exit or an application request.
I'm sure I'm doing a lot of things wrong here!
EDIT - Additional Test
I created a much more simplified test. A simple Windows Form application, which has 3 buttons. One to start the Task, one to show the Status, and one to stop the Task.
The good news is that I can stop the task with the CancellationToken, so I will investigate why this works, and the above example doesn't.
But if I show the status, I'm continuously seeing "WaitingForActivation". I still don't understand why this is not "Running". And after the task has been cancelled, the status shows "RanToCompletion", and not "Cancelled".
private void btnTestTask_Click(object sender, EventArgs e)
{
cancellationToken = cancellationTokenSource.Token;
myTask = Task.Run(() => Test(cancellationToken));
}
private void btnTestCancel_Click(object sender, EventArgs e)
{
cancellationTokenSource.Cancel();
}
private void btnShowStatus_Click(object sender, EventArgs e)
{
Debug.WriteLine($"Task Status: {myTask.Status}");
}
async static Task Test(CancellationToken cancellationToken)
{
while (true)
{
try
{
Debug.WriteLine("The task is running");
await Task.Delay(1000, cancellationToken);
}
catch (TaskCanceledException)
{
Debug.WriteLine("The task is cancelled");
break;
}
}
}