Question

I bit ago I was reading about delegates and events (for a completely different reason) from this [website][1] In there I got the impression that if your event takes long enough a seperate thread is generated. Well that got me thinking about a bug that I can't seem to fix. So I am making a keyboard wedge program for my MSR device that communicates via the RS232 port. I made this class to handle the input.

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

        serialPort = sp;
    }

I subscribe to the DataReceived event when I open this ComPortInput class. If I am guessing correctly then if i set my delay high enough then my dataevent would create a new thread. I think the problem is best described with looking at my code.

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. Just the datareceived event

    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}");
    }

On Clipboard.Clear() the program crashes with this error

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.

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)

I am at a loss as to why it is doing this. If i add a watch to the current thread appartment state sure enough it is MTA. Yet if i Put a break in the start of the program it says it is STA. So why did it switch? What even stumps me more is that if I use a different Output class it doesn't throw that error

SendRawClipboardOutput.cs

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

Nor does this one

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}");
    }

I dunno.. I am stumped. Any one care to tackle this problem?

EDIT

So with help I came up with this as my solution. Since SerialPortWedge is a class and not a control I was not able to call the Invoke method. I had to pass in SynchronizationContext.Current to my SerialPortWedge. So in my main form i have this after I instantiate my SerialPortWedge.

        msr.MainFormContext = SynchronizationContext.Current;

then in SerialPortWedge i changed my Input_DataAvailable to this

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

it now works as desired. Thank you for everyones help. :)

Was it helpful?

Solution

The problem is due to the fact that SerialPort.DataReceived is raised on a separate thread (always - it doesn't matter how long it takes). From the documentation:

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.

Basically, you need to marshal the call back to the UI thread when you receive data, so your other code will work properly.

void Input_DataAvailable(byte[] data)
{
    this.Invoke(new Action(() =>
    {
        Sound.Play();
        Output.SendData(data);
    }));
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top