C# Timer for STAThread COM calls in a Console App
I have a console application that needs to fire a method that uses COM. The program starts with [STAThread]. The program executes correctly when not using a timer process, but apparently suffers from blocking back to the console when using a Timer.
I've used System.Threading.Timer and System.Timers.Timer and n开发者_开发问答either worked. Presently I added a Thread calling the method (Transmit()) that uses COM. If I clear the main thread with the Console.Readline the program resumes where the COM object was blocked, but of course the program then closes and I lose the desired timer functionality.
I couldn't figure out how to set SynchronizingObject to get an ISynchronizeInvoke callback when using a console application.
I am not looking for multiple threads, I just need the Transmit method to be called on a regular interval and work with COM while returning results back to the console.
class Program
{
private static System.Timers.Timer transTimer;
[STAThread]
static void Main(string[] args)
{
transTimer = new System.Timers.Timer();
transTimer.Enabled = true;
transTimer.Interval = 6000;
transTimer.Elapsed += new System.Timers.ElapsedEventHandler(transTimer_Elapsed);
transTimer.Start();
Console.ReadLine();
transTimer.Dispose();
return;
}
static void transTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (transTimer.Enabled)
{
transTimer.Enabled = false;
Thread thread = new Thread(Transmit);
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
thread.Join(); //Wait for the thread to end
transTimer.Enabled = true;
}
}
You are violating an important Single Threaded Apartment (STA) requirement, it must pump a message loop. Without one, COM cannot marshal a call from a worker thread to the thread that created the COM component. This is necessary to ensure the component is used in a thread-safe way. The problem is not that you used a timer, the problem is that you used a thread. The observable effect is that the call deadlocks.
Getting a message loop in a console app is hard to come by. One workaround is to not create the COM component in your Main() method but to have the worker thread create it so that no marshaling is required. Implement the period calls by calling Sleep() in a loop.
Another workaround is to not use the [STAThread] attribute. COM will then automatically create an STA thread to give the COM component a safe home. This thread also pumps a message loop. Every call will now be marshaled, avoid if you make a lot of them.
The .NET way of setting up a message pump is Application.Run(). Call it from a seperate, dedicated thread and use the overload that does not take in a Form. Keep your COM code on that thread and your Console code on the Main thread and you should not have any problems with the Console blocking your COM logic. If you need a timer to interact with the COM logic, use a System.Windows.Forms.Timer object, created a started on the COM thread. That way the callbacks/events from it will be running on the COM thread too.
精彩评论