有人能指出一个使用.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();
}
}
}
答案 0 :(得分: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);
}
}
}
}
答案 1 :(得分:3)
我从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();
答案 2 :(得分:1)
我认为您的问题很多就是让用户触发ReadDataAsync
,并允许在仍在进行读取的过程中(从您的说明中)触发它。
执行此操作的正确方法是在打开串口时启动读取,并在读取的完成处理程序中启动另一个(好吧,检查完成是否由于关闭而导致完成)端口)。
无论如何,从串口同时读取都是无用的,因为您无法控制传入数据以完成例程的顺序。