简体   繁体   English

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

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

Can anyone point me to a working example that uses the .net 4.5 Async API (async, await, task<>, ReadAsync, etc) to do serial communications? 有人能指出我使用.net 4.5异步API(异步,等待,任务<>,ReadAsync等)进行串行通信的工作示例吗?

I've tried to adapt an existing event driven serial sample, and am getting all kinds of horrible behavior - "port in use by other application" errors, VS2013 debugger throwing exceptions and locking up - which usually require a PC reboot to recover from. 我试图调整现有的事件驱动的串行样本,并且得到各种可怕的行为 - “其他应用程序使用的端口”错误,VS2013调试器抛出异常和锁定 - 这通常需要PC重启才能从中恢复。

edit 编辑

I've written my own sample from scratch. 我从头开始编写自己的样本。 It's a simple Winforms project that writes to the Output window. 这是一个简单的Winforms项目,它写入Output窗口。 Three buttons on the form - Open Port, Close Port, and Read Data. 表单上的三个按钮 - 打开端口,关闭端口和读取数据。 The ReadDataAsync method calls SerialPort.BaseStream.ReadAsync. ReadDataAsync方法调用SerialPort.BaseStream.ReadAsync。

As of now, it will read data from the port, but I'm running into problems making it robust. 截至目前,它将从端口读取数据,但我遇到了使其变得强大的问题。

For example, if I unplug the serial cable, open the port, and click Read Data twice, I will get an System.IO.IOException (which I kind of expect), but my app stops responding. 例如,如果我拔下串行电缆,打开端口,然后单击Read Data两次,我将得到一个System.IO.IOException(我有点期待),但我的应用程序停止响应。

Worse, when I try to stop my program, VS2013 throws up a "Stop Debugging in Progress" dialog, which never completes, and I can't even kill VS from Task Manager. 更糟糕的是,当我尝试停止我的程序时,VS2013会抛出一个“正在进行停止调试”对话框,该对话框永远不会完成,我甚至无法从任务管理器中删除VS. Have to reboot my PC every time this happens. 每次发生这种情况都必须重启我的电脑。

Not good. 不好。

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

    }
}

I'd use TaskCompletionSource<> to wrap SerialDataReceivedEvent , something like this (untested): 我使用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);
            }
        }
    }
}

I've had similar problems closing a SerialPort from the UI thread. 从UI线程关闭SerialPort我遇到了类似的问题。 The following MSDN blog suggests that it's due to a deadlock between the UI thread and the native thread doing the closing. 以下MSDN博客表明,这是由于UI线程和执行关闭的本机线程之间的死锁。 http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx

Putting the close into a separate task fixed it for me. 把关闭放到一个单独的任务中为我修复它。 (The method is implemented in a Protocol container class in my project and called when a UI button is clicked, the IDispose interface invoked or the main window is closed.) (该方法在我的项目中的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;
    }

... and then in my UI command ... ...然后在我的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();

I think a good deal of your problem is having the user trigger ReadDataAsync , and allowing it to be triggered while there is still a read in progress (from your description). 我认为你的问题很多就是让用户触发ReadDataAsync ,并允许在仍有读取过程中触发它(来自你的描述)。

The right way to do this is to start a read when the serial port is opened, and in the completion handler for the read, start another one (well, check that the completion wasn't caused by closure of the port). 正确的方法是在打开串口时启动读取,并在读取的完成处理程序中启动另一个(好吧,检查完成是否不是由端口关闭引起的)。

Simultaneous reads from a serial port are useless anyway, because you can't control the order in which incoming data will be handed off to complete routines. 无论如何,从串口同时读取都是无用的,因为您无法控制传入数据以完成例程的顺序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM