C# 线程-多个SerialPort和一个DataGridView

C# 线程-多个SerialPort和一个DataGridView,c#,multithreading,event-handling,serial-port,queue,C#,Multithreading,Event Handling,Serial Port,Queue,我有3个SerialPort组件,其中2个有DataReceived处理程序。 当接收到数据时,它们通过其他SerialPort (第三个)。 我知道,在我的应用程序中,两个串行端口同时尝试发送的可能性不高,但我尝试模拟这种情况,当然,我得到一个错误,即正在使用COM端口 private void sendToCOM(String comNumber, String msg, String speed) { try { using

我有3个
SerialPort
组件,其中2个有
DataReceived
处理程序。 当接收到数据时,它们通过其他
SerialPort
(第三个)。 我知道,在我的应用程序中,两个
串行端口
同时尝试发送的可能性不高,但我尝试模拟这种情况,当然,我得到一个错误,即正在使用
COM端口

private void sendToCOM(String comNumber, String msg, String speed)
    {
        try
        {
            using (SerialPort comPort = new SerialPort(comNumber, Int32.Parse(speed), Parity.None, 8, StopBits.One))
            {
                comPort.Open();
                comPort.Write(msg);
                comPort.Close();
                comPort.Dispose();
            }
        }
        catch(Exception ex)
        {
                cstFuncs.errorHandler(ex.Message, SettingsForm);
        }
    }
我知道每个
SerialPort
都有自己的线程


如何创建队列或如何防止两个串行端口同时尝试访问相同的资源(并且不丢失应该发送的数据)

您可以做一些事情

1) 建立一个锁机制。在尝试访问该方法之前,线程将等待该锁被解锁,这意味着每个线程将轮流触发“sendToCOM”

2) 另一种方法是使用ConcurrentQueue(请参阅:)。这是一个线程安全的列表,如果需要,您也可以添加COMport对象,然后从那里启动它们。例如,关于这样做有几个问题


我相信还有更多的方法,但这是我脑子里想出来的两种方法。我认为第二种方法更可靠,但需要更多的工作才能开始运行,因此第一种方法可能是你最感兴趣的

1) 建立一个锁机制。在尝试访问该方法之前,线程将等待该锁被解锁,这意味着每个线程将轮流触发“sendToCOM”

2) 另一种方法是使用ConcurrentQueue(请参阅:)。这是一个线程安全的列表,如果需要,您也可以添加COMport对象,然后从那里启动它们。例如,关于这样做有几个问题


我相信还有更多的方法,但这是我脑子里想出来的两种方法。我想说第二个更可靠,但需要更多的工作才能运行,因此第一个可能是您最感兴趣的。

您可能没有每个串行端口都有一个线程,而是在线程池中执行
DataReceived
处理程序。这仍然意味着你可以一次读两遍

您可以使用锁定来解决此问题:

将OutputPortLock对象添加到类:

private object OutputPortLock = new object();
并在写入输出端口之前尝试锁定它:

lock(OutputPortLock)
{
    using(SerialPort comPort=....)
    {
        ... write to com port ...
    }
}

这将确保一次只有一个线程尝试写入输出端口。如果其他进程尝试使用Com端口,您可能仍然会收到Com端口使用错误。

您可能没有每个串行端口的线程,而是在线程池中执行
DataReceived
处理程序。这仍然意味着你可以一次读两遍

您可以使用锁定来解决此问题:

将OutputPortLock对象添加到类:

private object OutputPortLock = new object();
并在写入输出端口之前尝试锁定它:

lock(OutputPortLock)
{
    using(SerialPort comPort=....)
    {
        ... write to com port ...
    }
}

这将确保一次只有一个线程尝试写入输出端口。如果其他进程尝试使用Com端口,您可能仍然会收到Com端口使用错误。

有几种方法可以在
sendToCOM
方法中获得锁定。这将导致任何后续调用等待第一个调用完成,从而停止该方法执行两次。如果另一个进程尝试使用相同的
SerialPort
,这将没有帮助,但它将停止任何本地争用

一种方法是在如下对象上使用
lock
关键字:

private readonly object _myLock = new object();

private void sendToCOM(String comNumber, String msg, String speed)
{
    try
    {
        lock(_myLock)
        {
            using (SerialPort comPort = new SerialPort(comNumber, Int32.Parse(speed), Parity.None, 8, StopBits.One))
            {
                comPort.Open();
                comPort.Write(msg);
                comPort.Close();
                comPort.Dispose();
            }
        }
    }
    catch(Exception ex)
    {
        cstFuncs.errorHandler(ex.Message, SettingsForm);
    }
}
另一种方法是使用
ManualResetEventSlim
(或类似工具)来实现相同的效果:

private ManualResetEventSlim _myLock = new ManualResetEventSlim(true);

private void sendToCOM(String comNumber, String msg, String speed)
{
    _myLock.Wait();
    try
    {
        _myLock.Reset();
        using (SerialPort comPort = new SerialPort(comNumber, Int32.Parse(speed), Parity.None, 8, StopBits.One))
        {
            // ..
        }
    }
    catch(Exception ex)
    {
        cstFuncs.errorHandler(ex.Message, SettingsForm);
    } 
    finally
    {
        _myLock.Set();
    }
}

有几种方法可以锁定
sendToCOM
方法。这将导致任何后续调用等待第一个调用完成,从而停止该方法执行两次。如果另一个进程尝试使用相同的
SerialPort
,这将没有帮助,但它将停止任何本地争用

一种方法是在如下对象上使用
lock
关键字:

private readonly object _myLock = new object();

private void sendToCOM(String comNumber, String msg, String speed)
{
    try
    {
        lock(_myLock)
        {
            using (SerialPort comPort = new SerialPort(comNumber, Int32.Parse(speed), Parity.None, 8, StopBits.One))
            {
                comPort.Open();
                comPort.Write(msg);
                comPort.Close();
                comPort.Dispose();
            }
        }
    }
    catch(Exception ex)
    {
        cstFuncs.errorHandler(ex.Message, SettingsForm);
    }
}
另一种方法是使用
ManualResetEventSlim
(或类似工具)来实现相同的效果:

private ManualResetEventSlim _myLock = new ManualResetEventSlim(true);

private void sendToCOM(String comNumber, String msg, String speed)
{
    _myLock.Wait();
    try
    {
        _myLock.Reset();
        using (SerialPort comPort = new SerialPort(comNumber, Int32.Parse(speed), Parity.None, 8, StopBits.One))
        {
            // ..
        }
    }
    catch(Exception ex)
    {
        cstFuncs.errorHandler(ex.Message, SettingsForm);
    } 
    finally
    {
        _myLock.Set();
    }
}

非常感谢你的帮助。看起来是这样的。。我会读更多关于它的内容。非常感谢你的帮助。看起来是这样的。。我会读更多关于它的内容。谢谢你的帮助。这两种方法有什么区别?我尝试了第二种方法,它看起来很有效,但我成功地让它崩溃了一次(我有两个应用程序,每100ms向每个com端口发送一个字符串)。区别在于实际使用情况。我建议使用
lock
老实说,
ManualResetEventSlim
只是我一直在玩弄的一个想法,应该仍然有效。您确定崩溃是由此方法引起的吗?应用程序没有崩溃(“catch”捕捉到它),异常是
对端口“COM6”的访问被拒绝。
。在我尝试植入这种锁定(第二种方法)之前,我得到了很多。现在我得到的错误少了很多,但我仍然得到一些。。我现在要试试第一种方法。那确实很奇怪。老实说,我根本没有使用过SerialPorts,因此在这方面我无法给出更可靠的建议。然而,我知道这是不好的,但在开始时或在
最后
中人为引起的延迟可能会有所帮助?(谈论
Thread.Sleep
)我现在尝试了第一种方法,它减慢了整个过程(但我现在没有收到错误)也许正如您所建议的,稍微延迟一下,然后一起使用第二种方法可以解决问题。事实上,我并不需要这么高的速度,但是如果有一天需要的话,我会尽我最大的努力,谢谢你的帮助。这两种方法有什么区别?我尝试了第二种方法,它看起来很有效,但我成功地让它崩溃了一次(我有两个应用程序)