簡體   English   中英

使用.net 4.5中的Async API對串口通信代碼進行示例?

[英]Sample serial port comms code using Async API in .net 4.5?

有人能指出我使用.net 4.5異步API(異步,等待,任務<>,ReadAsync等)進行串行通信的工作示例嗎?

我試圖調整現有的事件驅動的串行樣本,並且得到各種可怕的行為 - “其他應用程序使用的端口”錯誤,VS2013調試器拋出異常和鎖定 - 這通常需要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<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();
        }

    }
}

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

從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 ,並允許在仍有讀取過程中觸發它(來自你的描述)。

正確的方法是在打開串口時啟動讀取,並在讀取的完成處理程序中啟動另一個(好吧,檢查完成是否不是由端口關閉引起的)。

無論如何,從串口同時讀取都是無用的,因為您無法控制傳入數據以完成例程的順序。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM