2013-06-18 3 views
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; 
    } 

Я подписываюсь на событие DataReceived, когда я открываю этот класс ComPortInput. Если я правильно угадываю, то, если бы я установил задержку достаточно высоко, то мой файл данных создаст новый поток. Я думаю, что проблему лучше всего описать, глядя на мой код.

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() программа падает с этой ошибкой

Текущий поток должен быть установлен в режим однопоточной квартиры (STA) до того, как вызовы OLE могут быть выполнены. Убедитесь, что ваша основная функция имеет 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}"); 
    } 

Я не знаю .. Я в тупике. Кто-нибудь может решить эту проблему?

EDIT

Так с помощью я придумал это как мое решение. Поскольку 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); 
    } 

теперь работает как хотелось бы. Спасибо всем за помощь. :)

+3

_I сложилось впечатление, что если ваше событие занимает достаточно много времени, генерируется отдельная нить ._ - очень плохое впечатление. –

+0

@HenkHolterman hahaha wow Я рад, что задаю вопросы. Я так многому научился. Спасибо, что объяснил это мне. –

ответ

5

Проблема связана с тем, что SerialPort.DataReceived поднят на отдельной резьбе (всегда - не имеет значения, сколько времени потребуется). Из документации:

Событие DataReceived возникает во вторичном потоке, когда данные получены от объекта SerialPort. Поскольку это событие возникает во вторичном потоке, а не в основном потоке, попытка изменить некоторые элементы основного потока, такие как элементы пользовательского интерфейса, может вызвать исключение потоковой передачи. Если необходимо изменить элементы в основной форме или элементе управления, отправьте запрос на изменение после использования Invoke, который будет выполнять работу над соответствующей нитью.

В принципе, вы должны маршировать вызов обратно в поток пользовательского интерфейса при получении данных, чтобы ваш другой код работал правильно.

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

У SerialPortWedge нет Invoke ... hmmm .. –

+1

@RobertSnyder Невозможно определить, куда его поместить, но что-то нужно его маршалировать. Вы можете передать SynchronizationContext.Current в один из ваших классов из формы и использовать это для публикации данных. –

+0

... так, например, я мог бы, чтобы моя основная форма отправляла Контекст (поскольку он создавал экземпляр SerialportWedge)? Я попробую. –