2014-04-22 262 views
4

任何人都可以指向一个使用.net 4.5异步API(异步,等待,任务<>,ReadAsync等)进行串行通信的工作示例吗?我试图改编现有的事件驱动的串行示例,并获取各种可怕的行为 - “其他应用程序使用的端口”错误,VS2013调试器抛出异常并锁定 - 通常需要PC重新启动从中恢复。使用.net 4.5中的Async API的串口通信代码示例?

编辑

我已经写了从无到有我自己的样品。这是一个写入输出窗口的简单的Winforms项目。表单上的三个按钮 - 打开端口,关闭端口和读取数据。 ReadDataAsync方法调用SerialPort.BaseStream.ReadAsync。

截至目前,它会从端口读取数据,但我遇到了问题,使其强大。例如,如果我拔掉串行电缆,打开端口,然后单击读取数据两次,我将得到一个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<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(); 
     } 

    } 
} 
+0

SerialPort没有异步方法,因为它本质上是异步性质的,它使用线程来处理通信,因此只需使用它的方法和事件就足够了。发布你的代码来检查它,看看有什么不对。还添加错误。 – Gusman

+0

其他SO帖子暗示我可以使用像SerialPort.BaseStream.ReadAsync等东西,我打电话给这个方法。你是说这不起作用吗? –

+0

嗯,从来没有使用SerialPort的底层流,但我怀疑它不会正常工作(也许我错了),我已经使用了多年的SerialPort并使用Write和Read函数和DataReceived事件应该已经足够并且已经异步。 – Gusman

回答

3

我会使用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<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); 
      } 
     } 
    } 
} 
+0

谢谢,我会试试这个。你认为这是比使用SerialPort.BaseStream.ReadAsync更好的方法吗? –

+0

@TomBushell,我认为这样可以更好地控制到达的数据。如果你需要缓冲(比如指定你想异步读取多少字节),那么你可以使用'ReadAsync'。您是否遇到过'ReadAsync'的问题? – Noseratio

+0

我现在在ReadAsync方面取得了一些成功,而且这是一种更简单的方法。然而,VS2013在遇到异常时有时会被锁定......这种异步的东西可能不会在黄金时段准备好...... :( –

1

我认为你的问题的一个很好的协议是有用户触发ReadDataAsync,并允许它是在仍有正在读取的情况下(从您的描述中)触发。

正确的做法是在串行端口打开时开始读取操作,并在读取完成处理程序中启动另一个读取操作(确保完成不是由关闭端口引起的)。

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

+0

任何想法为什么VS处理抛出的异常如此糟糕?假设它可能只是VS中的一个bug,但仍然... –

+0

@TomBushell:当你“拔掉串行电缆”时,这意味着你的串口连接到了你的电脑,而不是另一端的任何东西?或者你正在从电脑上拔下串口本身典型的USB串行端口)? –

+0

我正在将串口连接线从我想要控制的仪器的串行连接器上拔下来,我将USB连接到串口适配器插到电脑上。任何数据进入,并强制ReadAsync等待。 –

3

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

把接近一个单独的任务固定它给我。 (该方法在在我的项目一个协议容器类执行并调用被点击的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(); 
+0

顺便说一句,我知道'throw e''失去所有的调用堆栈并“抛出”;会更好 - 现在就是这样,因为这是一个放置软断点的好地方。 :-) –

+0

...和我的是一个Prolific USB到RS232电缆以及。 –

+0

“serialStream”是协议容器类的一个实例吗? –