c# - 防止事件产生另一个线程

标签 c# events polymorphism sta mta

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

    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 事件。如果我猜对了,那么如果我将延迟设置得足够高,那么我的数据事件将创建一个新线程。我认为最好通过查看我的代码来描述问题。

Program.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。只是数据接收事件

    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)

我不知道为什么要这样做。如果我向当前线程公寓状态添加一个监视,那么它肯定是 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);
    }

现在它可以按预期工作了。谢谢大家的帮助。 :)

最佳答案

问题是由于 SerialPort.DataReceived在单独的线程上引发(总是 - 无论需要多长时间)。来自文档:

The DataReceived event is raised on a secondary thread when data is received from the SerialPort object. Because this event is raised on a secondary thread, and not the main thread, attempting to modify some elements in the main thread, such as UI elements, could raise a threading exception. If it is necessary to modify elements in the main Form or Control, post change requests back using Invoke, which will do the work on the proper thread.

基本上,您需要在接收数据时将回调编码回 UI 线程,以便您的其他代码能够正常工作。

void Input_DataAvailable(byte[] data)
{
    this.Invoke(new Action(() =>
    {
        Sound.Play();
        Output.SendData(data);
    }));
}

关于c# - 防止事件产生另一个线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17175783/

相关文章:

c# - 如何计算 WPF 中客户区的偏移量?

c++11 - C++11 中的最终非多态类

php - 事件调度程序计算结果的 if 条件

c# - 是否可以编写一个类来隐藏添加 WeakEventManager 处理程序和传统处理程序之间的区别?

java - 如何使用相同的父类(super class)方法初始化不同的子类类型?

c++ - 多重或虚拟继承下的类的内存布局和vtable(s)?

c# - C# 中的 FileStream 和 FileSystemWatcher,奇怪的问题 "process cannot access the file"

c# - 创建并签署证书

c# - 将 CsvHelper 自定义转换器应用于一组类的所有字符串属性

java - Android MotionEvent 等同于 Java MouseEvent?