My c# WinForms application reading asynchronously from serial port. It works, but when the port closes, an StackOverflowException appears. The Call Stack is overflowed with line await ReadAsync(serialPort) on the end of ReadAsync. My guess is the line await ReadAsync still run even though I close SeriaPort, keepReading flag and checking serialPort.IsOpen. Hence the question, how to cancel await ReadAsync, is it possible? Appreciate some help here because I'm beginner.
public async Task ReadAsync(SerialPort serialPort)
{
if (serialPort.IsOpen && keepReading)
{
try
{
byte[] buffer = new byte[1];
await serialPort.BaseStream.ReadAsync(buffer, 0, 1);
content += serialPort.Encoding.GetString(buffer);
//do sth with content
buffer = null;
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
if (keepReading && serialPort.IsOpen)
await ReadAsync(serialPort);
}
}
private async void Button_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
keepReading = false;
serialPort.Dispose();
serialPort.Close();
}
else
{
Open();
keepReading = true;
if (serialPort.IsOpen)
{
serialPort.DiscardOutBuffer();
serialPort.DiscardInBuffer();
await ReadAsync(serialPort);
}
}
}
Try to wrap serialPort.BaseStream.ReadAsync(buffer, 0, 1); into another async method and use Task Cancellation to cancel this method before you close the serial port.
Cancel an Async Task or a List of Tasks (C#)
Related
Good day.
I'm having a problem exiting a task with the cancellation token.
My program freezes when I get to the token2.ThrowIfCancellationRequested();.
Following it with the breakpoints is shows that the token2 is cancelled, but the program doesn't revert back to the previous sub routine where I try and catch
try
{
Task.Run(() => SendData_DoWork(_tokenSource3));
}
catch (OperationCanceledException ex)
{
SetText("Communivation error with device");
SetText("");
}
finally
{
token.Dispose();
}
}//comms routine
//send Meter Address to communicate to meter
private void SendData_DoWork(CancellationTokenSource token)
{
var token2 = token.Token;
var _tokenSource4 = new CancellationTokenSource();
try
{
timer.Interval = 10000;
timer.Start();
timer.Elapsed += OnTimerElapsed;
NetworkStream stream = client.GetStream();
SerialConverter serialConverter = new SerialConverter();
Thread.Sleep(1000);
string newtext = null;
newtext = $"/?{address}!\r\n";
SetText("TX: " + newtext);
byte[] newData = stringSend(newtext);
stream.Write(newData, 0, newData.Length);
Thread.Sleep(50);
byte[] message = new byte[23];
int byteRead;
while (true)
{
byteRead = 0;
try
{
byteRead = stream.Read(message, 0, 23);
if (message[0] == (char)0x15)
{
token.Cancel();
}
}
catch
{
token.Cancel();
}
if ((byteRead == 0))
{
token.Cancel();
}
timer.Stop();
timer.Dispose();
ASCIIEncoding encoder = new ASCIIEncoding();
string newresponse = encoder.GetString(serialConverter.convertFromSerial(message));
SetText("RX: " + newresponse);
if (newresponse[0].ToString() == SOH)
{
token.Cancel();
}
if (newresponse != null)
{
/* NEXT SUB ROUTINE*/
}
else { break; }
}//while looop
}//try
catch (Exception ex)
{
token.Cancel();
}
if (token2.IsCancellationRequested)
{
timer.Stop();
timer.Dispose();
token2.ThrowIfCancellationRequested();
}
}//sendData subroutine
You are launching a Task, and ignoring the result; the only time Task.Run would throw is if the task-method is invalid, or enqueuing the operation itself failed. If you want to know how SendData_DoWork ended, you'll need to actually check the result of the task, by capturing the result of Task.Run and awaiting it (preferably asynchronously, although if we're talking async, SendData_DoWork should probably also be async and return a Task).
Your catch/finally will probably be exited long before SendData_DoWork even starts - again: Task.Run just takes the time required to validate and enqueue the operation; not wait for it to happen.
I think you have missunderstood how cancellation tokens are supposed to work. Your work method should take a CancellationToken, not a CancellationTokenSource. And it should call ThrowIfCancellationRequested inside the loop, not after. I would suspect that you would get some issues with multiple cancel calls to the same cancellation token.
Typically you would use a pattern something like like this:
public void MyCancelButtonHandler(...) => cts.Cancel();
public async void MyButtonHandler(...){
try{
cts = new CancellationTokenSource(); // update shared field
await Task.Run(() => MyBackgroundWork(cts.Token));
}
catch(OperationCancelledException){} // Ignore
catch(Exception){} // handle other exceptions
}
private void MyBackgroundWork(CancellationToken cancel){
while(...){
cancel.ThrowIfCancellationRequested();
// Do actual work
}
}
So in my particular case it seems like changing the sub-routines from private async void ... to private async Task fixes the particular issue that I'm having.
I have a GUI application where data is continously polled by MyMethod and when the Disconnect button is clicked, inside MyMethod the OperationCanceledException
closes the port. Here my_cancelationTokenSource is declared:
public partial class MainWindow : Window
{
private CancellationTokenSource my_cancelationTokenSource;
//...
}
And here MyMethod continously polls data:
private async Task MyMethod(ISerialComms port, byte[] test_telegram)
{
byte[] sent;
my_cancelationTokenSource = new CancellationTokenSource();
try
{
do
{
await Task.Delay(POLL_PERIOD, my_cancelationTokenSource.Token);
if (myFlag == false && sent != null && myPort.IsOpen == true && test_port == false)
{
byte[] received = await telegram.SendReceive(sent, myPort, port, my_cancelationTokenSource.Token);
MyProgressMethod(received, sent);
}
}
while (true);
}
catch (OperationCanceledException)
{
try
{
my_cancelationTokenSource.Dispose();
my_cancelationTokenSource = null;
if (myPort.IsOpen)
{
myPort.Close();
}
}
catch { }
}
}
Here is the disconnect button:
private void Button_Disconnect_Click(object sender, RoutedEventArgs e)
{
my_cancelationTokenSource?.Cancel();
}
And below I have another async method TestParameters() run in series inside another button:
private async void Test_Click(object sender, RoutedEventArgs e)
{
try
{
if (myPort.IsOpen)
{
myFlag = true;
byte[] set_parameter = myObject.CreatePulseParameters((double)NumericUpDown1.Value, (double)NumericUpDown2.Value);
int delay_ms = 20;
await Task.Delay(delay_ms);
await TestParameters(set_parameter);
await Task.Delay(delay_ms * 2);
await TestParameters(set_parameter);
await Task.Delay(delay_ms);
await TestParameters(set_parameter);
await Task.Delay(delay_ms * 4);
await TestParameters(set_parameter);
await Task.Delay(delay_ms);
await TestParameters(set_parameter);
await Task.Delay(delay_ms);
await TestParameters(set_parameter);
await Task.Delay(delay_ms * 3);
await TestParameters(set_parameter);
myFlag = false;
}
}
catch (System.InvalidOperationException)
{
myPort.Close();
}
}
My problem is, when I click Disconnect the port is closed but TestParameters() keeps running. How can this issue be solved?
(One solution was to put if(myPort.IsOpen) before each TestParameters(), but somehow I thought there misght be a more reilable better way)
Wht I need: TestParameters() should stop running as well when the Disconnect button is clicked just like MyMetho().
Edit:
Two other Tasks:
private async Task TestParameters(byte[] set_parameter)
{
if (myPort.IsOpen)
{
int times_try = 5;
int retry = 0;
myFlag = true;
await Task.Delay(500);
do
{
bool validate = await Set_Parameters(set_parameter);
await Task.Delay(500);
if (validate)
break;
if (!validate)
retry++;
}
while (retry < times_try);
}
myFlag = false;
}
And the other one:
private async Task<bool> Set_Parameters(byte[] send_set_parameter_telegram)
{
bool validate = false;
byte[] validate_parameters = pulse.validate_parameters;
if (myPort.IsOpen )
{
await Task.Delay(100);
byte[] receive_error_telegram = await telegram.SendReceive(validate_parameters, myPort, port);
await Task.Delay(100);
byte[] receive_validated_telegram = await telegram.SendReceive(validate_parameters, myPort, port);
}
return validate;
}
So I am talking to something externally and I have just sent it a message, I will expect an almost immediate response but I will wait for a second in case there is a delay. A separate thread is monitoring for an input and will set a flag "newdataflag" when it has received this data. All I am trying to do below is wait in a while loop until this flag is set or 1 second elapses.
private bool WaitrxData()
{
System.Windows.Threading.DispatcherTimer waitrxtimer = new System.Windows.Threading.DispatcherTimer();
waitrxtimer.Tick += waitrxtimer_Tick;
waitrxtimer.Interval = TimeSpan.FromMilliseconds(10);
waitrxtimer.IsEnabled = true;
waitrxtimer.Start();
statusText.Text = "Waiting For Response";
//wait for new data
while (!newdataflag)
{
if (waitrxcounter > 100)
{
statusText.Text = "No Response";
break;
}
}
waitrxtimer.Stop();
if (waitrxcounter > 100)
{
return false;
}
else
{
newdataflag = false;
return true;
}
}
private void waitrxtimer_Tick(object sender, EventArgs e)
{
waitrxcounter++;
}
This code works if the response is so immediate the while loop is skipped but if not, the code will not execute the timer and just get stuck in the while loop and crash. I think this is because the timer is not creating a new thread to tick like I thought it would?
Maybe I am doing the wrong thing?
Cheers
Instead of busy waiting in a loop you could await Task.Delay() with a CancellationToken:
private CancellationTokenSource cts = new CancellationTokenSource();
...
private async Task<bool> Wait()
{
try
{
await Task.Delay(1000, cts.Token);
return true;
}
catch
{
return false;
}
}
// cancel Wait() from another method
cts.Cancel();
I don't understand what information I have available to me with async operations in C# even after reading the docs. I have a TpcClient and I want it to try to connect x number of times. So far:
public async Task SocketConnect() {
tcpClient = new TcpClient();
for(int i = 0; i < maxConnectionAttempts; i++) {
OpenSocket();
await Task.Delay(5000);
}
}
private void OpenSocket() {
try {
tcpClient.BeginConnect(host, port, ConnectCallback, tcpClient);
}
catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private void ConnectCallback(IAsyncResult result) {
try {
tcpClient.EndConnect(result);
// Connected
if (tcpClient.Connected) {
Console.WriteLine("connected");
if (OnClientEvent != null)
OnClientEvent(this, new ClientEventArgs(Action.Connect));
stream = tcpClient.GetStream();
BeginReadAsync();
}
// Not connected
else {
Console.WriteLine("not connected");
Console.WriteLine("Retrying");
}
}
catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
I'm missing the logic in the SocketConnect() method. I'm not sure how I could await OpenSocket() because its result is carried to a different callback. What can I return from OpenSocket() so that I know it connected?
What a muddle you've gotten into.
You're mixing two different types of asynchrony... the Task Asynchronous Pattern and the (considerably more confusing) Asynchronous Programming Model. I suggest you ditch APM (it's old and sh*t) and stick with TAP from herein, because it allows you to write asynchronous code without callbacks so you end up much simpler, readable code.
If I understand correctly, all you're trying to do is this:
public async Task SocketConnect() {
var tcpClient = new TcpClient();
for(var retries = 0; retries < 5; retries++)
{
try
{
await tcpClient.ConnectAsync(host, port);
}
catch(Exception ex)
{
//handle errors
continue;
}
if(tcpClient.Connected) break;
}
if(tcpClient.Connected)
{
//yay
}
}
I have a TCP server running which spits out messages of 2 bytes at regular intervals.
I'm trying to create a client side form which connects to the server and continuously reads from the stream until I click a disconnect button on the form.
So far the client works fine except that I cannot disconnect. I set the CancellationPending to true but it seems to reset back to false before the dowork method gets a chance to set e.Cancel.
I'm also sure there must be a more acceptable way of continuously reading the stream and writing to the form - at the moment I am calling RunWorkerAsync within the Worker Completed method to achieve the loop!
private void Disconnect()
{
commsWorker1.CancelAsync();
}
private void ReadFromStream()
{
try
{
commsWorker1.RunWorkerAsync();
}
catch (Exception ex)
{
writeToBox("Error: " + ex.Message);
}
}
//background worker dowork method
private void BackGroundGetServerData(object sender, DoWorkEventArgs e)
{
if (true == commsWorker1.CancellationPending)
{
e.Cancel = true;
}
else
{
Byte[] dataArray = new Byte[2];
try
{
_DataStream.Read(dataArray, 0, 2);
String reply = System.Text.Encoding.ASCII.GetString(dataArray);
e.Result = reply;
}
catch (Exception ex)
{
}
}
}
//background worker workercompleted method
private void BackGroundDisplayMessages(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
//close connection here
}
else
{
writeToBox((String)e.Result);
commsWorker1.RunWorkerAsync();
}
}
}
}
Can't you just loop inside the background worker method?
private void BackGroundGetServerData(object sender, DoWorkEventArgs e)
{
while(true)
{
Byte[] dataArray = new Byte[2];
try
{
_DataStream.Read(dataArray, 0, 2);
String reply = System.Text.Encoding.ASCII.GetString(dataArray);
e.Result = reply;
}
catch (Exception ex)
{
return;
}
}
}
Then upon disconnect simply close the socket. This will cause the Exception to be thrown in the while loop and you can exit gracefully through the catch block.
Edit: Then you can update the GUI from the loop after each message is read. Make sure the handle to the control you are updating is available (assuming it's called box):
delegate void updateDelegate(String p);
private void BackGroundGetServerData(object sender, DoWorkEventArgs e)
{
while(true)
{
Byte[] dataArray = new Byte[2];
try
{
_DataStream.Read(dataArray, 0, 2);
String reply = System.Text.Encoding.ASCII.GetString(dataArray);
box.BeginInvoke(new updateDelegate(writeToBox), reply);
}
catch (Exception ex)
{
return;
}
}
}
BeginInvoke is required in this case because you are trying to update the GUI from another thread, which is not allowed. This method forwards the update to the GUI thread.
It seems that you are invoking RunWorkerAsync() in the worker complete method and that resets your CancellationPending prop. I think you can try to fix this by adding to Disconnect() method some disconnectFlag = true; and in WorkerComplete method you should add:
if (e.Cancelled || disconnectFlag)
{
disconnectFlag = false;
//close connection here
} else ...