I created two separate Windows Forms applications in C# that use MSMQ for communicating. Here's how it works, it looked simple enough though:
App1 sends a details request to App2.
App2 creates an event to open the window.
App2 opens a "details" window.
The only problem I have is that when received the message, the "details" window freezes after appearing.
As I handle MSMQ messages handling in an object that uses threads, I suspect the problem comes from there... But I have no experience in handling MSMQ messages or specific events handling between parts of an application.
Here's part of the code I use for App2:
/*Class declared in the Core namespace*/
public class TaskMessageQueueHandler
{
public TaskMessageQueueHandler()
{
this.Start();
}
private Thread m_thread;
private ManualResetEvent m_signal;
public event System.EventHandler messageReceived;
public void Start()
{
m_signal = new ManualResetEvent(false);
m_thread = new Thread(MSMQReceiveLoop);
m_thread.Start();
}
public void Stop()
{
m_signal.Set();
}
protected virtual void SendEvent(object sender, EventArgs e)
{
if (messageReceived != null)
messageReceived(this.message, e);
}
public string message;
private void MSMQReceiveLoop()
{
bool running = true;
MessageQueue queue = new MessageQueue(#".\Private$\queue1");
while (running)
{
try
{
var message = queue.Receive();
message.Formatter = new XmlMessageFormatter(new String[] { "System.String,mscorlib" });
this.message = message.Body.ToString();
string m = this.message;
SendEvent(m, System.EventArgs.Empty);
if (m_signal.WaitOne(10))
{
running = false;
}
}
catch
{
Console.WriteLine("ERROR");
running = false;
}
}
}
}
/*Main process, in the Program namespace*/
[...]
Core.TaskMessageQueueHandler tmqh = new Core.TaskMessageQueueHandler();
EventListener el = new EventListener();
tmqh.messageReceived += new System.EventHandler(el.ShowDetails);
[...]
/* Class in the Program namespace */
class EventListener
{
public void ShowDetails(object sender, EventArgs e)
{
int numero = int.Parse(sender as string);
Details details = new Details(numero);
details.Show();
}
}
Where did I go wrong? Where did I go right?
Thanks a lot,
Stephane.P
EDIT: if the MSMQ handler is stopped with Stop() anywhere around the event sending, the details window appears then disappears right away...
EDIT2: After the workaround given by Slugart, I managed to make this work:
class EventListener
{
Main control;
public EventListener(Main main)
{
control = main;
}
public void ShowDetails(object sender, EventArgs e)
{
int numero = int.Parse(sender as string);
control.Invoke((Action)(() => ShowDetails(numero)));
}
private void ShowDetails(int numero)
{
Details details = new Details(numero);
details.Show();
}
}
Which is used like:
Core.TaskMessageQueueHandler tmqh = new Core.TaskMessageQueueHandler();
EventListener el = new EventListener(this);
tmqh.messageReceived += new System.EventHandler(el.ShowDetails);
You're creating and displaying a form Details on a thread other than the main GUI thread and not an STA thread at that.
Your EventListener should have a reference to a running form (your main form perhaps) and then call form.Invoke() on it.
class EventListener
{
Control control; // A valid running winforms control/form created on an STA thread.
public void ShowDetails(object sender, string message)
{
int numero = int.Parse(message);
control.Invoke(() => ShowDetails(numero))
}
private void ShowDetails(int numero)
{
Details details = new Details(numero);
details.Show();
}
}
Also sending your event data as the sender is not really following the Event pattern that has been put in front of you. You want to use the EventArgs parameter for this, use the EventHandler delegate (EventHandler in your case).
Related
I'm struggling to pass data between a thread started in a separate class from my main form. I believe (I could be wrong) that I should use an event. The problem I have is my subscribers are always null as I call the BluetoothScan class and start the thread before the event is subscribed to:
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
How do I subscribe to the event before starting the thread?
I have my Main Form:
using System;
using System.Windows.Forms;
//https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0#System_Windows_Forms_Control_Invoke_System_Delegate_System_Object___
namespace YieldMonitor
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
}
private void BtnConnectBT_Click(object sender, EventArgs e)
{
//Start looking for the yield monitor device.
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
}
static void OnBluetoothDeviceDiscovered(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Message recieved from event");
}
}
}
My class that looks for bluetooth devices and if the right one is found should fire the event:
using InTheHand.Net.Sockets;
using System;
using System.Linq;
namespace YieldMonitor
{
class BluetoothScan
{
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
Run();
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Running BluetoothScan Class");
string myDeviceName;
ulong myDeviceAddress;
BluetoothClient btClient = new BluetoothClient();
BluetoothDeviceInfo[] btDevices = btClient.DiscoverDevices().ToArray();
foreach (BluetoothDeviceInfo d in btDevices)
{
System.Diagnostics.Debug.WriteLine(d.DeviceName);
System.Diagnostics.Debug.WriteLine(d.DeviceAddress);
//have we found the device we are looking for?
if (d.DeviceName == "DSD TECH HC-05")
{
myDeviceName = d.DeviceName;
myDeviceAddress = d.DeviceAddress;
//Send out found adapter to the next stage
OnBluetoothScanned(EventArgs.Empty);
break;
}
}
}
protected virtual void OnBluetoothScanned(EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Running OnBlueToothScanned");
EventHandler handler = BluetoothDeviceDiscovered;
if (handler != null)// we have a subscriber to our event
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Not empty");
handler(this, e);
}
else
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Empty");
}
}
}
}
EDIT
I've found some nice solutions using Tasks where I need to update a label once a task is completed ie.
bool myDevicePaired = false;
var eventDevicePaired = new Progress<bool>(boDevicePaired => myDevicePaired = boDevicePaired);
await Task.Factory.StartNew(() => BluetoothPair.Run(myDeviceAddress, eventDevicePaired), TaskCreationOptions.LongRunning);
//Register the device is paired with the UI
if (myDevicePaired)
{
BtnConnectBT.Text = "Disconnect?";
}
Which is working well for Tasks that have an end that I am waiting for example waiting for a bluetooth device to connect.
But I'm beginning to pull my hair out with System.InvalidOperationException: 'Cross-thread operation not valid: Control 'tbInfo' accessed from a thread other than the thread it was created on.' error when trying to update a form text box.
Example:
in my MainForm Class:
I create what I've called an Event Reciever...
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!"); //writes data to debug fine
tbInfo.AppendText("Event!!!!"); //causing error
}
I create a task to read from the device...
private void ReadDataFromDevice(UInt64 myDeviceAddress)
{
BluetoothSocket bluetoothSocket = new BluetoothSocket(myDeviceAddress);
bluetoothSocket.BluetoothDataRecieved += BluetoothSocketEventReciever;
Task.Factory.StartNew(() => bluetoothSocket.Run(), TaskCreationOptions.LongRunning);
}
In my BluetoothSocket class I have an endless while loop which will be reading data from a socket (hopefully) At the moment its just creating an empty EventArgs to trigger the Event every second:
namespace YieldMonitor
{
class BluetoothSocket
{
ulong myDeviceAddress;
public event EventHandler BluetoothDataRecieved;
public BluetoothSocket (ulong deviceAddress)
{
myDeviceAddress = deviceAddress;
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Were in BluetoothSocket ... Address: " + myDeviceAddress);
while (true)
{
Thread.Sleep(1000);
Debug.WriteLine("In BluetoothSocket - Address = " + myDeviceAddress);
OnBluetoothDataRecieved(EventArgs.Empty);
}
}
protected virtual void OnBluetoothDataRecieved(EventArgs e)
{
EventHandler handler = BluetoothDataRecieved;
if (handler != null)
{
handler(this, e);
} else
{
//No subscribers
}
}
}
}
I'm sure I'm missing something simple here but how can I pass the data from the endless loop to the text box on the main form?
EDIT
Think I've just sorted it.
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!");
tbInfo.Invoke((Action)delegate
{
tbInfo.AppendText("Event!!!");
});
//tbInfo.AppendText("Event!!!!");
}
Is this the correct way to do it?
You can Pass the event handler as a parameter on the constructor
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm, EventHandler bluetoothDeviceDiscovered)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
BluetoothDeviceDiscovered += bluetoothDeviceDiscovered
Run();
}
Personally, i'm not so fun of calling method on constructor. It can be source of bugs or performance issues
Constructor
In class-based object-oriented programming, a constructor
(abbreviation: ctor) is a special type of subroutine called to create
an object. It prepares the new object for use, often accepting
arguments that the constructor uses to set required member variables.
You can pass eventhandler as parameter and call Run later
in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?
I have a C# WinForms application with using of WCF Data Services and I can't find a way to know if the application is doing a process to a web service. I have tried to implement IClientMessageInspector, etc. but it seems that it doesn't work.
Is there any other way to catch when a Windows Form app is consuming a web service? The purpose is to display a "running process" icon when the communication to a service is opened and then hide it when it's close.
Thanks.
you should add event in place where you calling WcfService methods and then listen to this event in UI layer. With custom event argument in this event, you should provide more info for consuming UI layer like enum values Connected, TransferStarted, TransferEnded, Closed,...
I think there are two main possible cases:
Wcf communication take place synchronous - you are calling some wrapper methods of object which handles connection/requests/responses with wcf service - in this case i think its trivial, you make UI actions before every call of wrapper for example
try
{
WcfEx.IwcfS5ExtensionClient client = new IwcfS5ExtensionClient("MyEndpointConfigurationName");
client.Open();
if (client.State == CommunicationState.Opened)
{
//change UI to Connected
}
else
{
//change ui to Connection Error
}
Application.DoEvents();
//Change UI to Transfering data
Application.DoEvents();
client.DoWork();
//change UI to transfer done
Application.DoEvents();
client.Close();
//change ui to Closed
}
catch (Exception e)
{
//change ui to Comunication error
}
Second is if wcf communication is async
-i will add more sofisticated sample later today
EDIT: Example with async work and notifying form
using System;
using System.ComponentModel;
using System.Threading;
using WcfEx;
public class MyForm : Form
{
public MyForm()
{
WcfHandler WcfConnection = new WcfHandler();
WcfConnection.ProgressChanged += WcfConnectionOnProgressChanged;
}
private delegate void WcfConnectionOnProgressChangedDelegate(object Sender, WcfHandler.ProgressChangedEventArgs EventArgs);
private void WcfConnectionOnProgressChanged(object Sender, WcfHandler.ProgressChangedEventArgs EventArgs)
{
//multi thread synchronization check
if (this.InvokeRequired)
{
object[] Parameters = new object[2];
Parameters[0] = Sender;
Parameters[1] = EventArgs;
this.Invoke(new WcfConnectionOnProgressChangedDelegate(WcfConnectionOnProgressChanged), Parameters);
}
else
{
if (EventArgs == null)
return;
switch (EventArgs.StateValue)
{
case WcfHandler.ProgressChangedEventArgs.State.Started:
{
this.Text = "Starting connection...";
break;
}
case WcfHandler.ProgressChangedEventArgs.State.Processing:
{
this.Text = "Downloading updates...";
break;
}
case WcfHandler.ProgressChangedEventArgs.State.Finished:
{
this.Text = EventArgs.Succes ? "Update completed" : "Update failed";
break;
}
}
Application.DoEvents();
}
}
public class WcfHandler
{
public class ProgressChangedEventArgs : EventArgs
{
public enum State : int
{
Started,
Processing,
Finished
};
public bool Succes { get; set; }
public State StateValue { get; set; }
}
public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs EventArgs);
public event ProgressChangedEventHandler ProgressChanged;
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
if (ProgressChanged != null)
{
ProgressChanged(this, e);
}
}
public void StartChecking()
{
BackgroundWorker bWorker = new BackgroundWorker();
bWorker.DoWork += CheckStatesAsync;
bWorker.RunWorkerCompleted += BWorkerOnRunWorkerCompleted;
bWorker.RunWorkerAsync();
}
private void CheckStatesAsync(object sender, DoWorkEventArgs e)
{
while (true)
{
WcfEx.IwcfS5ExtensionClient client = new IwcfS5ExtensionClient("MyWcfBindingConfig");
ProgressChangedEventArgs Controller = new ProgressChangedEventArgs();
Controller.StateValue = ProgressChangedEventArgs.State.Started;
Controller.Succes = true;
this.OnProgressChanged(Controller);
try
{
client.Open();
Controller.StateValue = ProgressChangedEventArgs.State.Processing;
Controller.Succes = true;
this.OnProgressChanged(Controller);
//do some work
}
catch (Exception)
{
this.OnProgressChanged(new ProgressChangedEventArgs()
{
StateValue = ProgressChangedEventArgs.State.Finished,
Succes = false
});
}
Thread.Sleep(8000);
}
}
private void BWorkerOnRunWorkerCompleted(object Sender, RunWorkerCompletedEventArgs RunWorkerCompletedEventArgs)
{
ProgressChangedEventArgs Controller = new ProgressChangedEventArgs();
Controller.StateValue = ProgressChangedEventArgs.State.Finished;
Controller.Succes = true;
this.OnProgressChanged(Controller);
}
}
I'm a beginner at using ActiveMQ with C#. I've created a simple windows form with one button and one label. When I click on the button, i send a message to the queue and the label is initialized with the message I just sent. Of course, I could initialize my label directly but I want my form to rather consume the message from the queue in order to update my label.
The problem is I don't manage to handle the message in the same form to update my label. My consumer code is not called at all and yet, its initialized in the Load event of my form.
Here's the code
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
InitializeHandlerAMQ();
}
private void InitializeHandlerAMQ()
{
Tchat tchat = null;
IDestination dest = _session.GetQueue(QUEUE_DESTINATION);
using(IMessageConsumer consumer = _session.CreateConsumer(dest))
{
IMessage message;
while((message = consumer.Receive(TimeSpan.FromMilliseconds(2000))) != null)
{
var objectMessage = message as IObjectMessage;
if(objectMessage != null)
{
tchat = objectMessage.Body as Tchat;
if (tchat != null)
{
textBox2.Text += string.Format("{0}{1}", tchat.Message, Environment.NewLine);
}
}
}
}
}
If I close my windows form and restart it, then my label is well updated but I don't want to close it and re open it.
Do you have any ideas guys ?
Try creating a class with an event delegate like this.
A subscriber class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Apache.NMS.ActiveMQ.Commands;
namespace Utilities
{
public delegate void QMessageReceivedDelegate(string message);
public class MyQueueSubscriber : IDisposable
{
private readonly string topicName = null;
private readonly IConnectionFactory connectionFactory;
private readonly IConnection connection;
private readonly ISession session;
private readonly IMessageConsumer consumer;
private bool isDisposed = false;
public event QMessageReceivedDelegate OnMessageReceived;
public MyQueueSubscriber(string queueName, string brokerUri, string clientId)
{
this.topicName = queueName;
this.connectionFactory = new ConnectionFactory(brokerUri);
this.connection = this.connectionFactory.CreateConnection();
this.connection.ClientId = clientId;
this.connection.Start();
this.session = connection.CreateSession();
ActiveMQQueue topic = new ActiveMQQueue(queueName);
//this.consumer = this.session.CreateDurableConsumer(topic, consumerId, "2 > 1", false);
this.consumer = this.session.CreateConsumer(topic, "2 > 1");
this.consumer.Listener += new MessageListener(OnMessage);
}
public void OnMessage(IMessage message)
{
ITextMessage textMessage = message as ITextMessage;
if (this.OnMessageReceived != null)
{
this.OnMessageReceived(textMessage.Text);
}
}
#region IDisposable Members
public void Dispose()
{
if (!this.isDisposed)
{
this.consumer.Dispose();
this.session.Dispose();
this.connection.Dispose();
this.isDisposed = true;
}
}
#endregion
}
}
Winforms
In your windows form Subscribe to the queue like this
MyQueueSubscriber QueueSubscriber = new MyQueueSubscriber(QueueName, ActiveMQHost, QueueClientId);
QueueSubscriber.OnMessageReceived += new QMessageReceivedDelegate(QueueSubscriber_OnMessageReceived);
static void QueueSubscriber_OnMessageReceived(string message)
{
SetText(message);
}
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.labelname.value = text;
}
}
Resources:
Unfortunately there are not that many resources to teach C# & ActiveMQ. Try using http://activemq.apache.org/nms/ as this was quite good.
Try looking at a small article from http://www.codersource.net/MicrosoftNet/CAdvanced/PublishSubscribeinCusingActiveMQ.aspx. Disclaimer: This is my website and the article was written by me. Sorry for the self publicity. But I feel this is relevant to the topic.
I've made a little test program to try and get a USB card reader working using an ActiveX control provided by the manufacturer.
The form works fine as long as it doesn't use a separate thread. I create a new instance of Reader and listen to the Read event and everything works fine. I swipe a card, the event fires and the textbox gets updated.
private Reader _reader;
public Form1()
{
InitializeComponent();
CreateScanner();
}
public void CreateScanner()
{
_reader = new Reader();
_reader.Read += Read;
}
void Read(object sender, EventArgs e)
{
CardData.Text = "Card Read";
}
Reader class in case it helps:
public class Reader
{
private AxUSBHIDInsert _axUsbHid;
public event EventHandler Read;
public Reader()
{
_axUsbHid = new AxUSBHIDInsert();
_axUsbHid.CreateControl();
_axUsbHid.BeginInit();
_axUsbHid.MSRReadDir = MSRReadDirection.ReadWithdrawal;
_axUsbHid.EndInit();
_axUsbHid.PortOpen = true;
_axUsbHid.CardDataChanged += CardDataChanged;
}
void CardDataChanged(object sender, EventArgs e)
{
if (Read != null)
Read(this, new EventArgs());
}
}
However, I need to run this on a separate thread. So I change the constructor to be
Thread thread = new Thread(CreateScanner);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
It has to be an STA thread otherwise the ActiveX control will complain that it cannot be instantiated. However doing this, the event doesn't fire anymore. I'm not that familiar with how threading works, so I'm not sure why.
Any ideas? Note that it has to work this way (separate thread, hooked up to the Read event), because the code will eventually reside in a class library that gets deployed with an application that I cannot change.
Your COM object sends messages to the second thread, that means it must be alive all the time application is running.
Try to do like this:
public class Reader
{
public Reader()
{
// leave empty
}
public Read() {
_axUsbHid = new AxUSBHIDInsert();
...
}
}
public Form1()
{
InitializeComponent();
_reader = new Reader();
_reader.Read += Read;
StartRead(_reader);
}
void StartRead(Reader reader) {
Thread thread = new Thread(ReadRoutine);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(reader);
}
void ReadRoutine(object param) {
Reader reader = (Reader)param;
reader.Read();
while (AppIsAlive) { // add logic here
// bad code, import GetMessage from user32.dll
Application.DoEvents();
Thread.Sleep(100);
}
}
But the Read event must be processed synchronously:
void Read(object sender, EventArgs e)
{
if (this.InvokeRequired)
this.BeginInvoke(new EventHandler(Read), new object[2] { sender, e } );
else {
CardData.Text = "Card Read";
}
}