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
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);
}
}
}
}
I think a good deal of your problem is having the user trigger ReadDataAsync
, and allowing it to be triggered while there is still a read in progress (from your description).
The right way to do this is to start a read when the serial port is opened, and in the completion handler for the read, start another one (well, check that the completion wasn't caused by closure of the port).
Simultaneous reads from a serial port are useless anyway, because you can't control the order in which incoming data will be handed off to complete routines.
I've had similar problems closing a SerialPort from the UI thread. The following MSDN blog suggests that it's due to a deadlock between the UI thread and the native thread doing the closing. http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx
Putting the close into a separate task fixed it for me. (The method is implemented in a Protocol container class in my project and called when a UI button is clicked, the IDispose interface invoked or the main window is closed.)
public Task Close()
{
// Close the serial port in a new thread
Task closeTask = new Task(() =>
{
try
{
serialPort.Close();
}
catch (IOException e)
{
// Port was not open
throw e;
}
});
closeTask.Start();
return closeTask;
}
... and then in my UI command ...
// The serial stream is stopped in a different thread so that the UI does
// not get deadlocked with the stream waiting for events to complete.
await serialStream.Close();