.NET Windows Service needs to use STAThread
I have created a Windows Service that will be calling out to some COM components, so I tagged [STAThread] to the Main function. However, when the timer fires, it reports MTA and the COM calls fail. How can I fix this?
using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
using System.Timers;
namespace MyMonitorService
{开发者_开发百科
public class MyMonitor : ServiceBase
{
#region Members
private System.Timers.Timer timer = new System.Timers.Timer();
#endregion
#region Construction
public MyMonitor ()
{
this.timer.Interval = 10000; // set for 10 seconds
this.timer.Elapsed += new System.Timers.ElapsedEventHandler(this.timer_Elapsed);
}
#endregion
private void timer_Elapsed (object sender, ElapsedEventArgs e)
{
EventLog.WriteEntry("MyMonitor", String.Format("Thread Model: {0}", Thread.CurrentThread.GetApartmentState().ToString()), EventLogEntryType.Information);
}
#region Service Start/Stop
[STAThread]
public static void Main ()
{
ServiceBase.Run(new MyMonitor());
}
protected override void OnStart (string[] args)
{
EventLog.WriteEntry("MyMonitor", "My Monitor Service Started", EventLogEntryType.Information);
this.timer.Enabled = true;
}
protected override void OnStop ()
{
EventLog.WriteEntry("MyMonitor", "My Monitor Service Stopped", EventLogEntryType.Information);
this.timer.Enabled = false;
}
#endregion
}
}
Services are run by the windows service hosting system, which runs using MTA threads. You can't control this. You have to create a new Thread and set its ApartmentState to STA, and do your work on this thread.
Here's a class that extends ServiceBase that does this:
public partial class Service1 : ServiceBase
{
private System.Timers.Timer timer;
public Service1()
{
InitializeComponent();
timer = new System.Timers.Timer();
this.timer.Interval = 10000; // set for 10 seconds
this.timer.Elapsed += new System.Timers.ElapsedEventHandler(Tick);
}
protected override void OnStart(string[] args)
{
timer.Start();
}
private void Tick(object sender, ElapsedEventArgs e)
{
// create a thread, give it the worker, let it go
// is collected when done (not IDisposable)
var thread = new Thread(WorkerMethod);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
OnStop(); // kill the timer
}
private void WorkerMethod(object state)
{
// do your work here in an STA thread
}
protected override void OnStop()
{
timer.Stop();
timer.Dispose();
}
}
Note this code doesn't actually stop the service, it stops the timer. There could be lots of work still being done on multiple threads. For instance, if your work consisted of running multiple queries off a large database you may end up crashing because you have too many threads running at the same time.
In a situation like this, I'd create a set number of STA threads (maybe 2x the number of cores to start off with) which monitor a thread-safe queue for work items. The timer tick event would be responsible for loading that queue with the work needing done.
It all depends on what you're actually doing every ten seconds, whether or not it should be completed the next time the timer ticks, what you should do in this situation, etc etc.
That cannot work in a service, the thread that calls your Main() method was already started by the service manager. You'll need to create a separate thread that is initialized with Thread.SetApartmentState() and pumps a message loop.
Setting the STAThread attribute will not work on a service. It's not being handled the same way as an application, so this will get ignored.
My recommendation would be to manually make a separate thread for your service, set its apartment state, and move everything into it. This way, you can set the thread to STA correctly.
However, there will be another issue here - you'll have to rework the way your service works. You can't just use a System.Threading.Timer instance for timing - it runs on a separate thread, which will not be STA. When its elapsed event fires, you'll be working on a different, non-STA thread.
Instead of doing your work in the timer event, you'll probably want to do your main work in the thread you create explicitly. You can have a reset event in that thread which blocks, and have your timer "set" it to allow your logic to run in the STA thread.
Looking at a similar example: http://www.aspfree.com/c/a/C-Sharp/Creating-a-Windows-Service-with-C-Sharp-introduction/1/
What if your main is...
[STAThread]
public static void Main ()
{
MyMonitor m = new MyMonitor();
m.Start();
}
and move your timer start / stop out of the events...
public void Start() { this.timer.Enabled = true;}
public void Stop() { this.timer.Enabled = false;}
protected override void OnStart (string[] args)
{
EventLog.WriteEntry("MyMonitor", "My Monitor Service Started", EventLogEntryType.Information);
}
protected override void OnStop ()
{
EventLog.WriteEntry("MyMonitor", "My Monitor Service Stopped", EventLogEntryType.Information);
}
This reports that it is using STA. It is based on Will's suggestion and http://en.csharp-online.net/Creating_a_.NET_Windows_Service%E2%80%94Alternative_1:_Use_a_Separate_Thread
using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
namespace MyMonitorService
{
internal class MyMonitorThreaded : ServiceBase
{
private Boolean bServiceStarted = false;
private Thread threadWorker;
private void WorkLoop ()
{
while (this.bServiceStarted)
{
EventLog.WriteEntry("MyMonitor", String.Format("Thread Model: {0}", Thread.CurrentThread.GetApartmentState().ToString()), EventLogEntryType.Information);
if (this.bServiceStarted)
Thread.Sleep(new TimeSpan(0, 0, 10));
}
Thread.CurrentThread.Abort();
}
#region Service Start/Stop
protected override void OnStart (String[] args)
{
this.threadWorker = new Thread(WorkLoop);
this.threadWorker.SetApartmentState(ApartmentState.STA);
this.bServiceStarted = true;
this.threadWorker.Start();
}
protected override void OnStop ()
{
this.bServiceStarted = false;
this.threadWorker.Join(new TimeSpan(0, 2, 0));
}
#endregion
}
}
精彩评论