2014-04-22 264 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」是協議容器類的一個實例嗎? –