使用.net 4.5中的Async API对串口通信代码进行示例?

有人能指出我使用.net 4.5异步API(异步,等待,任务,ReadAsync等)进行串行通信的工作示例吗?

我试图调整现有的事件驱动的串行样本,并得到各种可怕的行为 – “其他应用程序正在使用的端口”错误,VS2013调试器抛出exception和锁定 – 这通常需要PC重启才能从中恢复。

编辑

我从头开始编写自己的样本。 这是一个简单的Winforms项目,它写入Output窗口。 表单上的三个按钮 – 打开端口,关闭端口和读取数据。 ReadDataAsync方法调用SerialPort.BaseStream.ReadAsync。

截至目前,它将从端口读取数据,但我遇到了问题,使其变得强大。

例如,如果我拔下串行电缆,打开端口,然后单击Read Data两次,我将得到一个System.IO.IOException(我有点期待),但我的应用程序停止响应。

更糟糕的是,当我尝试停止我的程序时,VS2013会抛出一个“正在进行停止调试”对话框,该对话框永远不会完成,我甚至无法从任务管理器中杀死VS. 每次发生这种情况都必须重启我的电脑。

不好。

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 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(); } } } 

我使用TaskCompletionSource<>来包装SerialDataReceivedEvent ,类似这样(未经测试):

 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( (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 FromEvent( Func, Action, Action, TEventHandler> getHandler, Action subscribe, Action unsubscribe, Action, Action, Action> initiate, CancellationToken token) where TEventHandler : class { var tcs = new TaskCompletionSource(); Action complete = (args) => tcs.TrySetResult(args); Action cancel = () => tcs.TrySetCanceled(); Action 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); } } } } 

从UI线程关闭SerialPort我遇到了类似的问题。 以下MSDN博客表明,这是由于UI线程和执行关闭的本机线程之间的死锁。 http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx

把关闭放到一个单独的任务中为我修复它。 (该方法在我的项目中的Protocol容器类中实现,并在单击UI按钮时调用,调用IDispose接口或关闭主窗口。)

  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; } 

…然后在我的UI命令中……

  // 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(); 

我认为你的问题很多就是让用户触发ReadDataAsync ,并允许在仍在进行读取的过程中触发它(来自你的描述)。

正确的方法是在打开串口时启动读取,并在读取的完成处理程序中启动另一个(好吧,检查完成是否不是由端口关闭引起的)。

无论如何,从串口同时读取都是无用的,因为您无法控制传入数据以完成例程的顺序。