SerialDevice.FromIdAsync()返回空的串口。

6
我尝试连接串口时,我的代码出现了奇怪的问题。使用Serial Sample时,一切正常,它可以在第一次尝试连接时成功。但是,我的代码返回一个空值。
我找到了一种方法,通过嵌套调用写入串口的语句,并循环执行直到串口不再为空,可以实现连接并且它可以工作。平均需要循环500次才能连接,但它总是能够连接成功。我知道这很危险,因为如果始终为空,则会出现无限循环,所以我尝试将do循环改成for循环。然而,使用for循环时,即使迭代50000次,它仍然返回空值。有其他人遇到类似的问题并找到更好的解决方案吗?我正在连接到Arduino Uno。
我没有使用Serial Sample代码,原因是我想使用下拉菜单,使用端口名称而不是设备ID名称。以下是我的代码。谢谢。
    //lists com ports
    private async void listPorts()
    {
        try
        {
            string aqs = SerialDevice.GetDeviceSelector();
            var dlist = await DeviceInformation.FindAllAsync(aqs);
            status.Text = "Select a device and connect";

            for (int i = 0; i < dlist.Count; i++)
            {
                var port = await SerialDevice.FromIdAsync(dlist[0].Id);
                ConnectDevices.Items.Add(port.PortName);
            }

            comConnect.IsEnabled = true;
            ConnectDevices.SelectedIndex = 0;
        }
        catch (Exception ex)
        {
            status.Text = ex.Message;
        }
    }

    //connects to the selected com port
    private async void comConnect_Click(object sender, RoutedEventArgs e)
    {
        _key = 0;
        status2.Text = "";
        string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        if (dlist.Count <= 0)
        {
            status.Text = "No devices found.";
            return;
        }
        try
        {
            do
            {
                serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
                status.Text = "Connecting to serial port...";
            } while (serialPort == null);

            // Configure serial settings
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 38400;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;
            status.Text = "Serial port configured successfully.";

我已将以下内容添加到package.appmanifest文件中。
<Capabilities>
 <DeviceCapability Name="serialcommunication">
  <Device Id="any">
   <Function Type="name:serialPort" />
  </Device>
 </DeviceCapability>
</Capabilities>

更新:com端口被listport()方法占用。当我打开我的应用程序时,无法上传到我的设备(Arduino Uno)。但是,当我打开串行示例应用程序时,只要不按连接按钮,我仍然可以上传到它。我应该能够在我的应用程序中做同样的事情,但我不能。 - John Muse
3个回答

11
在我的项目中,当我添加以下代码到package.appmanifest后,串口正常工作。
    <Capabilities>
     <DeviceCapability Name="serialcommunication">
      <Device Id="any">
       <Function Type="name:serialPort" />
      </Device>
     </DeviceCapability>
    </Capabilities>

串口变量应该是"serialPort"。


谢谢 - 我忘记了,如果您在设计器中编辑清单,则会删除在代码视图中添加的项目。 - Tim Tyler

2
这里的问题(你已经正确地识别了)是你的 listPorts 一直保持着对 SerialPort 的连接开放状态。

SerialDevice 类为您管理串口,并确保每个端口在任何时候只能由一个进程使用。 在这种情况下,多次调用通过创建 SerialDevice,会返回 null 直到你释放设备。

SerialDevice 实现了 IDisposable 接口,因此你应该在 using 中包装对它的调用,以便在代码超出作用域时正确释放连接。

//lists com ports
private async void listPorts()
{
    try
    {
        string aqs = SerialDevice.GetDeviceSelector();
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        status.Text = "Select a device and connect";

        for (int i = 0; i < dlist.Count; i++)
        {
            using(var port = await SerialDevice.FromIdAsync(dlist[0].Id))
            {
                ConnectDevices.Items.Add(port.PortName);
            }
        }

        comConnect.IsEnabled = true;
        ConnectDevices.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
}

使用上述代码更改后,您可以删除comConnect_Click逻辑中的无限循环:

    // NOTE: possible infinite loop!
    //do
    //{
    status.Text = "Connecting to serial port...";
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    //
    //} while (serialPort == null);

之前它能够工作的原因是,在listPorts执行一段时间后,其超出范围,垃圾收集器将销毁port变量资源并释放物理串行端口连接。

如果您的代码不再占用连接,则等待可能占用连接的外部进程确实可能是无限的,或者至少是不确定的。

  • It would be a better pattern in this case to abort the connection process:
        status.Text = "Connecting to serial port...";
        serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
        if(serialPort == null)
        {
            status.Text = "Port busy, please try again later";
            return;
        }
    

注意:设备选择

在没有使用非常特定的设备选择查询来确保列表中只有一个(或零个)项目时,应避免硬编码设备数组选择,例如dlist [0] .Id

如果您正在按照 OP 的具体代码进行操作,请确保进行唯一选择。不要简单地使用列表中的第一个项目,特别是如果您使用这种代码枚举了 SerialDevices。

string aqs = Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector();
var dlist = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(aqs, null);

这个查询通常会返回索引为零的本地PC,然后是每个串行连接的设备。

为避免这种问题,请保存对您想要操作的列表中特定项的引用,而不是硬编码索引。

var selectedDevice = dlist[0];

这是在代码中诊断空值或数组索引问题的更明显的点。

关于无限循环

只有在异步环境或应用程序中,许多进程(或许多相同进程的实例)需要访问SerialPort,或者您希望允许同一操作系统上的外部进程在需要时访问SerialPort时,才应该担心创建SerialPort周围的循环结构。但是,您应该添加一些检查以允许中止过程。

如果您确实想要允许重试,则应限制重试次数或允许重试过程等待的时间段。您甚至可以同时执行两个操作,将所有内容包装在一起,您可能会得到以下内容,以及一些诊断信息可供检查:

var timer = Stopwatch.StartNew();
int attempts = 0;
do
{
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    attempts++;
    if(serialPort != null)
        break;
    await Task.Delay(1000); // pause between retries
} while (serialPort == null && timer.ElapsedMilliseconds < 10000 && attempts < 5); 
// max wait 10 seconds, or 5 total retries for serial device coms to come online.
timer.Stop();
Debug.WriteLine($"Connection to SerialPort {(serialPort == null ? "FAILED" : "Successs")} in: {timer.Elapsed} ({attempts} attempts)");

if(serialPort == null)
{
    status.Text = "Port busy, please try again later";
    return;
}

您的应用程序结构是这样的,您希望保持对 SerialPort 的独占连接,并阻止其他可能想要使用同一设备的进程访问,在这种情况下,当您使用Connect按钮连接时,您不需要处理SerialPort,但您应该确保在您的窗体的处理方法中清理serialPort实例变量。

如果相反,您将所有调用都结构化为在需要时始终创建新的SerialPort连接,则需要在完成后处理对象。在此模式中,如果您正在实现一个 while 循环以连接到设备,而不是使用语句,您可以使用 try-finally 模式:

//connects to the selected com port
private async void comConnect_Click(object sender, RoutedEventArgs e)
{
    _key = 0;
    status2.Text = "";
    string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
    var dlist = await DeviceInformation.FindAllAsync(aqs);
    if (dlist.Count <= 0)
    {
        status.Text = "No devices found.";
        return;
    }
    try
    {
        // NOTE: possible infinite loop!
        do
        {
            serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
            status.Text = "Connecting to serial port...";
        } while (serialPort == null);

        // Configure serial settings
        serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
        serialPort.BaudRate = 38400;
        serialPort.Parity = SerialParity.None;
        serialPort.StopBits = SerialStopBitCount.One;
        serialPort.DataBits = 8;
        serialPort.Handshake = SerialHandshake.None;
        status.Text = "Serial port configured successfully.";

        // Perform other serial logic... 
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
    finally
    {
        if(serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }
}

0

这段代码在我的串口和Arduino上运行良好:

string aqs = SerialDevice.GetDeviceSelector("COM3");
DeviceInformationCollection dlist = await DeviceInformation.FindAllAsync(aqs);

if (dlist.Any())
{
 deviceId = dlist.First().Id;
}

using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId))
{
// .....
}

尝试指定 COM 端口

谢谢。使用那个确实有效。我可以禁用listport方法并输入我正在使用的com端口,它会正常工作。问题真正在于调用var port = await SerialDevice.FromIdAsync(dlist[0].Id);。使用它似乎比应该更长时间地保持COM端口开放。我只是不想让代码过于特定于我的机器。我想我可以制作一个COM端口列表,然后从中选择正确的端口。 - John Muse
有时候在某个地方添加延迟/暂停可能会有所帮助。 - Alexej Sommer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接