net 4.5中使用Async API的串行端口通信代码示例

本文关键字:串行端口 通信 代码 API Async net | 更新日期: 2023-09-27 17:59:42

有人能给我指一个使用.net 4.5 Async API(异步、等待、任务<>、ReadAsync等)进行串行通信的工作示例吗?

我试图调整现有的事件驱动的串行示例,但却遇到了各种可怕的行为——"其他应用程序正在使用端口"错误、VS2013调试器抛出异常和锁定——通常需要重新启动电脑才能恢复。

编辑

我从头开始写我自己的样本。这是一个简单的Winforms项目,用于写入Output窗口。表单上的三个按钮-打开端口、关闭端口和读取数据。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();
        }
    }
}

net 4.5中使用Async API的串行端口通信代码示例

我会使用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,并允许它在仍在进行读取时被触发(根据您的描述)。

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

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