2

不久前,我正在从这个 [网站][1] 中阅读有关委托和事件(出于完全不同的原因)的信息,在那里我得到的印象是,如果您的事件花费的时间足够长,则会生成一个单独的线程。好吧,这让我想到了一个我似乎无法修复的错误。所以我正在为我的 MSR 设备制作一个键盘楔形程序,通过 RS232 端口进行通信。我做了这个类来处理输入。

    public ComPortInput(SerialPort sp, int delay)
        : this(delay)
    {
        if (sp == null)
            throw new System.ArgumentNullException("Serial port can not be null");

        serialPort = sp;
    }

当我打开这个 ComPortInput 类时,我订阅了 DataReceived 事件。如果我猜对了,那么如果我将延迟设置得足够高,那么我的 dataevent 将创建一个新线程。我认为最好通过查看我的代码来描述问题。

程序.cs

    [STAThread]
    static void Main()
    {
        singleton = new System.Threading.Mutex(true, "Keymon");
        if (!singleton.WaitOne(System.TimeSpan.Zero, true))
        { return; }

        InstantiateProgram();
        System.Windows.Forms.Application.Run(main);
    }
    private static void InstantiateProgram()
    {
        System.Windows.Forms.Application.EnableVisualStyles();
        System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
        main = new frmMain();
    }

ComPortInput.cs。只是 datareceived 事件

    void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (Delay > 0)
            System.Threading.Thread.Sleep(Delay); //waits for all the data to be sent to the card.

        int bytesRead = serialPort.BytesToRead;
        if (bytesRead > 0)
        {
            byte[] buffer = new byte[bytesRead];
            serialPort.Read(buffer, 0, bytesRead);

            OnDataAvailable(buffer);
        }
    }

SerialPortWedge.cs

    void Input_DataAvailable(byte[] data)
    {
        Sound.Play();
        Output.SendData(data);
    }

格式化十六进制字符串输出

    public override void SendData(byte[] buffer)
    {
        string str = "";
        for(int i=0; i<buffer.Length; i++)
        {
            if ((i+16)%16==0)
            {
                str += string.Format("{1}{0}: ", i.ToString("X3"), System.Environment.NewLine);
            }
            str += string.Format("{0}:", buffer[i].ToString("X2"));
        }
        Clipboard.Clear();
        Clipboard.SetText(str);
        SendKeys.SendWait("^v");
        SendKeys.SendWait("{ENTER}");
    }

Clipboard.Clear()程序因此错误而崩溃

在进行 OLE 调用之前,必须将当前线程设置为单线程单元 (STA) 模式。确保您的 Main 函数上标记了 STAThreadAttribute。

at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32 retryDelay)
at System.Windows.Forms.Clipboard.Clear()
at SerialPortDataSender.FormattedHexStringOutput.SendData(Byte[] buffer) in c:\SerialPortDataSender\Output\FormattedHexStringOutput.cs:line 28
at SerialPortDataSender.SerialPortWedge.Input_DataAvailable(Byte[] data) in c:\SerialPortDataSender\SerialPortWedge.cs:line 34
at SerialPortDataSender.IInput.OnDataAvailable(Byte[] data) in c:\SerialPortDataSender\Input\IInput.cs:line 41
at SerialPortDataSender.ComPortInput.sp_DataReceived(Object sender, SerialDataReceivedEventArgs e) in c:\SerialPortDataSender\Input\ComPortInput.cs:line 135
at System.IO.Ports.SerialPort.CatchReceivedEvents(Object src, SerialDataReceivedEventArgs e)
at System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents(Object state)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

我不知道它为什么这样做。如果我在当前线程 appartment 状态中添加了一个手表,那肯定是 MTA。然而,如果我在程序的开始处打断它说它是 STA。那么它为什么会切换呢?更让我难过的是,如果我使用不同的输出类,它不会抛出那个错误

SendRawClipboardOutput.cs

    public override void SendData(byte[] buffer)
    {
        Clipboard.Clear();
        Clipboard.SetText(System.Text.ASCIIEncoding.ASCII.GetString(buffer));
        SendKeys.SendWait("^v");
    }

这个也没有

SendTrimClipboardOutput.cs

    public override void SendData(byte[] buffer)
    {
        var str = System.Text.ASCIIEncoding.ASCII.GetString(buffer);
        str = str.Replace(System.Environment.NewLine, "");
        Clipboard.Clear();
        Clipboard.SetText(str);
        SendKeys.SendWait("^v");
        SendKeys.SendWait("{ENTER}");
    }

我不知道..我很难过。有人关心解决这个问题吗?

编辑

所以在帮助下,我想出了这个作为我的解决方案。由于 SerialPortWedge 是一个类而不是控件,我无法调用 Invoke 方法。我必须传递SynchronizationContext.Current给我的 SerialPortWedge。所以在我的主要形式中,我在实例化我的 SerialPortWedge 后得到了这个。

        msr.MainFormContext = SynchronizationContext.Current;

然后在 SerialPortWedge 我将 Input_DataAvailable 更改为此

    void Input_DataAvailable(byte[] data)
    {
        if(MainFormContext != null)
            MainFormContext.Send(FireEventFromContext, data);
    }
    private void FireEventFromContext(object state)
    {
        Sound.Play();
        Output.SendData((byte[])state);
    }

它现在可以按需要工作。谢谢大家的帮助。:)

4

1 回答 1

5

SerialPort.DataReceived问题是由于在单独的线程上引发的事实(总是- 不管需要多长时间)。从文档中:

从 SerialPort 对象接收数据时,在辅助线程上引发 DataReceived 事件。由于此事件是在辅助线程而不是主线程上引发的,因此尝试修改主线程中的某些元素(例如 UI 元素)可能会引发线程异常。如果需要修改主窗体或控件中的元素,请使用 Invoke 将更改请求发回,这将在适当的线程上完成工作。

基本上,您需要在接收数据时将调用编组回 UI 线程,这样您的其他代码才能正常工作。

void Input_DataAvailable(byte[] data)
{
    this.Invoke(new Action(() =>
    {
        Sound.Play();
        Output.SendData(data);
    }));
}
于 2013-06-18T18:05:11.403 回答