Sample serial port comms code using Async API in .net 4.5?
.net, async-await, asynchronous, c#, serial-port
Solution
I'd use `TaskCompletionSource<>` to wrap `SerialDataReceivedEvent`, something like this (untested):
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
class PortDataReceived
{
public static async Task ReadPort(SerialPort port, CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
await TaskExt.FromEvent<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
(complete, cancel, reject) => // get handler
(sender, args) => complete(args),
handler => // subscribe
port.DataReceived += handler,
handler => // unsubscribe
port.DataReceived -= handler,
(complete, cancel, reject) => // start the operation
{ if (port.BytesToRead != 0) complete(null); },
token);
Console.WriteLine("Received: " + port.ReadExisting());
}
}
public static void Main()
{
SerialPort port = new SerialPort("COM1");
port.BaudRate = 9600;
port.Parity = Parity.None;
port.StopBits = StopBits.One;
port.DataBits = 8;
port.Handshake = Handshake.None;
port.Open();
Console.WriteLine("Press Enter to stop...");
Console.WriteLine();
var cts = new CancellationTokenSource();
var task = ReadPort(port, cts.Token);
Console.ReadLine();
cts.Cancel();
try
{
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.InnerException.Message);
}
port.Close();
}
// FromEvent<>, based on http://stackoverflow.com/a/22798789/1768303
public static class TaskExt
{
public static async Task<TEventArgs> FromEvent<TEventHandler, TEventArgs>(
Func<Action<TEventArgs>, Action, Action<Exception>, TEventHandler> getHandler,
Action<TEventHandler> subscribe,
Action<TEventHandler> unsubscribe,
Action<Action<TEventArgs>, Action, Action<Exception>> initiate,
CancellationToken token) where TEventHandler : class
{
var tcs = new TaskCompletionSource<TEventArgs>();
Action<TEventArgs> complete = (args) => tcs.TrySetResult(args);
Action cancel = () => tcs.TrySetCanceled();
Action<Exception> reject = (ex) => tcs.TrySetException(ex);
TEventHandler handler = getHandler(complete, cancel, reject);
subscribe(handler);
try
{
using (token.Register(() => tcs.TrySetCanceled()))
{
initiate(complete, cancel, reject);
return await tcs.Task;
}
}
finally
{
unsubscribe(handler);
}
}
}
}
Problem
Can anyone point me to a working example that uses the .net 4.5 Async API (async, await, task<>, ReadAsync, etc) to do serial communications? I've tried to adapt an existing event driven serial sample, and am getting all kinds of horrible behavior - "port in use by other application" errors, VS2013 debugger throwing exceptions and locking up - which usually require a PC reboot to recover from. edit I've written my own sample from scratch. It's a simple Winforms project that writes to the Output window. Three buttons on the form - Open Port, Close Port, and Read Data. The ReadDataAsync method calls SerialPort.BaseStream.ReadAsync. As of now, it will read data from the port, but I'm running into problems making it robust. For example, if I unplug the serial cable, open the port, and click Read Data twice, I will get an System.IO.IOException (which I kind of expect), but my app stops responding. Worse, when I try to stop my program, VS2013 throws up a "Stop Debugging in Progress" dialog, which never completes, and I can't even kill VS from Task Manager. Have to reboot my PC every time this happens. Not good. ``` using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO.Ports; namespace WindowsFormsApplication1 { public partial class Form1 : Form { private SerialPort _serialPort; public Form1() { InitializeComponent(); } private void openPortbutton_Click(object sender, EventArgs e) { try { if (_serialPort == null ) _serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); if (!_serialPort.IsOpen) _serialPort.Open(); Console.Write("Open..."); } catch(Exception ex) { ClosePort(); MessageBox.Show(ex.ToString()); } } private void closePortButton_Click(object sender, EventArgs e) { ClosePort(); } private async void ReadDataButton_Click(object sender, EventArgs e) { try { await ReadDataAsync(); } catch (Exception ex) { ClosePort(); MessageBox.Show(ex.ToString(), "ReadDataButton_Click"); } } private async Task ReadDataAsync() { byte[] buffer = new byte[4096]; Task<int> readStringTask = _serialPort.BaseStream.ReadAsync(buffer, 0, 100); if (!readStringTask.IsCompleted) Console.WriteLine("Waiting..."); int bytesRead = await readStringTask; string data = Encoding.ASCII.GetString(buffer, 0, bytesRead); Console.WriteLine(data); } private void ClosePort() { if (_serialPort == null) return; if (_serialPort.IsOpen) _serialPort.Close(); _serialPort.Dispose(); _serialPort = null; Console.WriteLine("Close"); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { ClosePort(); } } } ```