我之前我正在阅读关于代表和事件(完全不同的原因)来自[网站] [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将创建一个新线程。我认为最好通过查看我的代码来描述问题。
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。只是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);
}
FormattedHexStringOutput
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);
}
它现在可以正常工作。谢谢大家的帮助。 :)
答案 0 :(得分:5)
问题是由于SerialPort.DataReceived
在一个单独的线程上引发的事实(总是 - 它需要多长时间)。来自文档:
从SerialPort对象接收数据时,在辅助线程上引发DataReceived事件。因为此事件是在辅助线程而不是主线程上引发的,所以尝试修改主线程中的某些元素(例如UI元素)可能会引发线程异常。如果有必要修改主窗体或控件中的元素,请使用Invoke发回更改请求,这将在正确的线程上完成工作。
基本上,您需要在接收数据时将回调编组回UI线程,这样您的其他代码才能正常工作。
void Input_DataAvailable(byte[] data)
{
this.Invoke(new Action(() =>
{
Sound.Play();
Output.SendData(data);
}));
}