admin管理员组文章数量:1410689
Sorry to ask what is probably a simple answer but I've spent a couple of hours looking through documentation and can't seem to get my code working how I think it should work, I've not used C# in years either which isn't helping.
What I'm trying to write a simple C# application that monitors a serial port for incoming data and displays it in a text box.
I'm trying to use a thread to run the serial port method and have it start / stop with a button.
The closest I have it working is now, but when you press the stop button it throws an exception
System.ObjectDisposedException: 'The CancellationTokenSource has been disposed.'
I thought it should just stop the thread but it throws an exception, wrapping it in a try/catch the application keeps running but the server fails to stop.
Any pointers for problems or a better / alternative way of doing it or any help is greatly appreciated, maybe I'm going about the approach all wrong. I'd like to learn though. Thanks in advance if you made it this far!
private void btn_start_Click(object sender, EventArgs e)
{
using var cts = _cts = new();
try
{
Thread thread = new(() => serialserver(_cts.Token));
thread.Start();
}
catch
{
}
btn_start.Enabled = false;
btn_stop.Enabled = true;
}
public void serialserver(CancellationToken token)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Started")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
while (!token.IsCancellationRequested)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("TEST! I'm running")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
Thread.Sleep(2500);
// do some more serial stuff here
}
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Stopped")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
}
private void btn_stop_Click(object sender, EventArgs e)
{
_cts!.Cancel();
_cts = null;
btn_start.Enabled = true;
btn_stop.Enabled = false;
}
Sorry to ask what is probably a simple answer but I've spent a couple of hours looking through documentation and can't seem to get my code working how I think it should work, I've not used C# in years either which isn't helping.
What I'm trying to write a simple C# application that monitors a serial port for incoming data and displays it in a text box.
I'm trying to use a thread to run the serial port method and have it start / stop with a button.
The closest I have it working is now, but when you press the stop button it throws an exception
System.ObjectDisposedException: 'The CancellationTokenSource has been disposed.'
I thought it should just stop the thread but it throws an exception, wrapping it in a try/catch the application keeps running but the server fails to stop.
Any pointers for problems or a better / alternative way of doing it or any help is greatly appreciated, maybe I'm going about the approach all wrong. I'd like to learn though. Thanks in advance if you made it this far!
private void btn_start_Click(object sender, EventArgs e)
{
using var cts = _cts = new();
try
{
Thread thread = new(() => serialserver(_cts.Token));
thread.Start();
}
catch
{
}
btn_start.Enabled = false;
btn_stop.Enabled = true;
}
public void serialserver(CancellationToken token)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Started")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
while (!token.IsCancellationRequested)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("TEST! I'm running")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
Thread.Sleep(2500);
// do some more serial stuff here
}
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Stopped")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
}
private void btn_stop_Click(object sender, EventArgs e)
{
_cts!.Cancel();
_cts = null;
btn_start.Enabled = true;
btn_stop.Enabled = false;
}
Share
Improve this question
edited Mar 11 at 18:25
marc_s
756k184 gold badges1.4k silver badges1.5k bronze badges
asked Mar 11 at 12:46
Wes PriceWes Price
3566 silver badges12 bronze badges
1
- The following may be of interest: stackoverflow/a/79252620/10024425 – It all makes cents Commented Mar 11 at 15:03
3 Answers
Reset to default 1Your post is a bit tricky because it asks about stopping a thread (we'll call that "X") but strongly implies that reading SerialPort
data is the ultimate goal (that we'll call "Y"). You said:
Any pointers for problems or a better / alternative way of doing it or any help is greatly appreciated.
In keeping with this, I'll try and share what's worked for me for both X and for Y.
Stopping the Thread (X)
If the code that you posted is the code you wish to keep (i.e. having a polling loop) then you might want to experiment with making the loop asynchronous so that you don't lose the UI thread context while the background work proceeds on an alternate thread. In this snippet:
- The Task.Delay makes use of the
CancellationToken
to exit the delay immediately. - We make a point of catching the
OperationCancelled
exception. - At the top of each loop iteration, we have the token
throw
if it's been cancelled.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
checkBoxToggleServer.CheckedChanged += async (sender, e) =>
{
if (checkBoxToggleServer.Checked)
{
_cts?.Cancel();
// Wait for previous run (if any) to cancel
await _awaiter.WaitAsync();
_cts = new CancellationTokenSource();
try
{
txtbox_log.AppendText("Serial Server Started", true, Color.Green);
while (true)
{
_cts.Token.ThrowIfCancellationRequested();
var timestamp = $@"[{DateTime.Now:hh\:mm\:ss\ tt}] ";
txtbox_log.AppendText($"{timestamp} TEST! I'm running", true, Color.Blue);
await Task.Delay(TimeSpan.FromSeconds(1.5), _cts.Token); // Taken from posted code.
await Task.Run(() =>
{
// "do some more serial stuff here"
_cts.Token.ThrowIfCancellationRequested();
}, _cts.Token);
}
}
catch (OperationCanceledException)
{
txtbox_log.AppendText("Serial Server Canceled", true, Color.Maroon);
checkBoxToggleServer.Checked = false;
_awaiter.Wait(0);
_awaiter.Release();
}
}
else
{
if (_cts is not null && !_cts.IsCancellationRequested) _cts.Cancel();
}
};
}
SemaphoreSlim
_awaiter = new SemaphoreSlim(1, 1),
_criticalSection = new SemaphoreSlim(1, 1);
CancellationTokenSource? _cts = null;
}
Where AppendText
is an Extension for RichTextBox.
static class Extensions
{
public static void AppendText(this RichTextBox @this, string text, bool newLine, Color? color = null)
{
var colorB4 = @this.SelectionColor;
if(color is Color altColor) @this.SelectionColor = altColor;
@this.AppendText($"{text}{(newLine ? Environment.NewLine : string.Empty)}");
@this.SelectionColor = colorB4;
}
}
Reading Serial Port Data
What I have found is that retrieving asynchronous data from a SerialPort
takes on a different flavor because we're often listening to the DataReceived
event and responding on an interrupt basis. This code snippet:
- Opens or Closes the Serial Port based on the button toggle.
- Is listening for data to become available by subscribing to the
DataReceived
event (in this case, using an inline Lambda method). - Marshals the data onto the UI thread in order to display it.
public partial class MainForm : Form
{
SerialPort _serialPort = new();
public MainForm()
{
InitializeComponent();
_serialPort.DataReceived += async (sender, e) =>
{
await _criticalSection.WaitAsync();
if (!IsDisposed) BeginInvoke((MethodInvoker)delegate
{
try
{
if (sender is SerialPort port)
{
while (port.BytesToRead > 0)
{
byte[] buffer = new byte[Math.Min(port.BytesToRead, 16)];
int success = port.Read(buffer, 0, buffer.Length);
BeginInvoke(() =>
{
txtbox_log.AppendText($@"[{DateTime.Now:hh\:mm\:ss.ff tt}] ", false, Color.CornflowerBlue);
txtbox_log.AppendText( BitConverter.ToString(buffer, 0, success).Replace("-", " "), true);
});
}
}
}
finally
{
_criticalSection.Release();
}
});
};
checkBoxToggleServer.CheckedChanged += (sender, e) =>
{
if (checkBoxToggleServer.Checked)
{
_serialPort.Open();
txtbox_log.AppendText($"Serial Server Started", true, Color.Green);
}
else
{
_serialPort.Close();
txtbox_log.AppendText("Serial Server Canceled", true, Color.Maroon);
}
};
}
SemaphoreSlim
_awaiter = new SemaphoreSlim(1, 1),
_criticalSection = new SemaphoreSlim(1, 1);
CancellationTokenSource? _cts = null;
}
The using
will make sure that the CancellationTokenSource will be disposed at the end of your method btn_start_Click
. But you will need it later in your method btn_stop_Click
. Hence you must not dispose it in the start method. Remove the using there and dispose in your stop method.
private void btn_start_Click(object sender, EventArgs e)
{
var cts = _cts = new(); // no using, otherwise Dispose will be called at the very end of this method
try
{
Thread thread = new(() => serialserver(_cts.Token));
thread.Start();
}
catch
{
}
btn_start.Enabled = false;
btn_stop.Enabled = true;
}
private void btn_stop_Click(object sender, EventArgs e)
{
_cts!.Cancel();
_cts.Dispose(); // it is important to call dispose here
_cts = null;
btn_start.Enabled = true;
btn_stop.Enabled = false;
}
The problem is that you dispose the CancellationTokenSource
but the stop button handler is still using it.
This should be resolved using this pattern:
using var cts = this._cts = new();
try
{
// here your should wait for the task/thread to finish otherwise this._cts is disposed.
}
finally
{
this._cts = null;
}
The stop button handler should use:
this._cts?.Cancel();
Maybe an implementation could be:
public async Task SerialServerAsync(CancellationToken token = default)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Started")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
while (true)
{
Invoke((MethodInvoker)(() => txtbox_log.AppendText("TEST! I'm running")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
await Task.Delay(2500, token);
// do some more serial stuff here
}
Invoke((MethodInvoker)(() => txtbox_log.AppendText("Serial Server Stopped")));
Invoke((MethodInvoker)(() => txtbox_log.AppendText(Environment.NewLine)));
}
private async void btn_start_Click(object? sender, EventArgs e)
{
btn_start.Enabled = false;
btn_stop.Enabled = true;
try
{
using var cts = _cts = new();
try
{
await Task.Run(() => this.SerialServerAsync(cts.Token));
}
catch (OperationCancelledException)
{
}
catch
{
}
}
finally
{
btn_start.Enabled = true;
btn_stop.Enabled = false;
}
}
/// Stop-Handle: only this._cts?.Cancel();
本文标签: Can39t get a thread to stop via button in C and WinformsStack Overflow
版权声明:本文标题:Can't get a thread to stop via button in C# and Winforms - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744794276a2625482.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论