2010-01-04 10 views
14

He creado un servicio de Windows que llamará a algunos componentes COM, así que etiqueté [STAThread] a la función principal. Sin embargo, cuando se dispara el temporizador, informa MTA y las llamadas COM fallan. ¿Cómo puedo arreglar esto?.NET El servicio de Windows necesita usar STAThread

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 
    } 
} 

Respuesta

24

Los servicios son administrados por el sistema de hospedaje del servicio de Windows, que se ejecuta utilizando subprocesos MTA. No puedes controlar esto. Debe crear un nuevo Thread y set its ApartmentState to STA, y hacer su trabajo en este hilo.

Aquí es una clase que se extiende ServiceBase que hace esto:

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

Nota este código en realidad no detener el servicio, se detiene el temporizador. Aún podría haber mucho trabajo por hacer en varios hilos. Por ejemplo, si su trabajo consistió en ejecutar múltiples consultas fuera de una base de datos grande, puede terminar colisionando porque tiene demasiados hilos ejecutándose al mismo tiempo.

En una situación como esta, crearía un número determinado de subprocesos STA (tal vez 2x la cantidad de núcleos para empezar) que monitorean una cola segura para subprocesos para elementos de trabajo. El evento tick del temporizador sería responsable de cargar esa cola con el trabajo que necesita realizarse.

Todo depende de lo que en realidad está haciendo cada diez segundos, si es o no debe completarse la próxima vez que el temporizador de garrapatas, lo que debe hacer en esta situación, etc, etc

+1

+1 para la solución alternativa; sin embargo, ¿cómo se explica que exista el mismo problema para las aplicaciones de consola? Según tengo entendido, tu explicación parece no ser correcta. Un servicio se inicia como un proceso secundario del administrador de control de servicio (y no como un hilo) y luego el servicio se conecta al controlador llamando a 'StartServiceCtrlDispatcher' (por lo que puedo ver en Reflector) para comenzar un servicio como STA. posible. –

+1

No existe para aplicaciones de consola. Las aplicaciones de la consola respetan el STAThreadAttribute. Los servicios no. El SCM no se ejecuta en un hilo STA. El SCM no respeta ni proporciona ningún método para que los servicios comuniquen el estado de su apartamento deseado. El código del OP es confuso, ya que incluye el método Principal. Todo lo que el método Principal hace es decir, "Oye, SCM, aquí hay una DLL que contiene un tipo de servicio que quiero que ejecutes, kthxbai" y se va. Sí, eso se ejecuta en un hilo STA, pero no se bloquea y vuelve de inmediato. El SCM luego determina cuándo ejecutar el servicio utilizando sus propios hilos. – Will

+0

Will, Después de buscar un poco más en la web y su respuesta, tengo una nueva estructura a continuación (no había suficientes caracteres para dejarla como un comentario) –

4

Eso no puede funcionar en un servicio, el hilo que llama a su método Main() ya fue iniciado por el administrador del servicio. Tendrá que crear un hilo separado que se inicialice con Thread.SetApartmentState() y active un ciclo de mensajes.

+0

creo que el problema radica en el apartamento de enhebrado del hilo temporizador porque me sale el mismo problema en una aplicación de consola (hilo principal es STA, pero en evento de temporizador del apartamento roscado es MTA). –

+0

Sí, el temporizador va a ser un hilo de MTA. Me dirigí a esto específicamente en mi respuesta. –

0

Mirando un ejemplo similar: http://www.aspfree.com/c/a/C-Sharp/Creating-a-Windows-Service-with-C-Sharp-introduction/1/

Qué ocurre si la principal es ...

[STAThread] 
    public static void Main() 
    { 
     MyMonitor m = new MyMonitor(); 
     m.Start(); 
    } 

y mover el inicio del temporizador/parada de los acontecimientos ...

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

Establecer el atributo STAThread no funcionará en un servicio. No se maneja de la misma manera que una aplicación, por lo que se ignorará.

Mi recomendación sería crear manualmente un hilo separado para su servicio, configurar su estado de departamento y mover todo dentro de él. De esta forma, puede establecer el hilo en STA correctamente.

Sin embargo, habrá otro problema aquí: tendrá que volver a trabajar en la forma en que funciona su servicio. No puede usar simplemente una instancia System.Threading.Timer para el tiempo: se ejecuta en un hilo separado, que no será STA. Cuando se dispare su evento transcurrido, trabajará en un hilo diferente que no sea STA.

En lugar de hacer su trabajo en el evento de temporizador, es probable que desee hacer su trabajo principal en la secuencia que cree explícitamente. Puede tener un evento de reinicio en ese hilo que bloquea, y tener su temporizador "configurado" para permitir que su lógica se ejecute en el hilo STA.

+1

Hay muchos ejemplos en la web usando STAThread en el principal de un servicio. ¿Están equivocados? –

+0

La mayoría de ellos no funcionan, sin embargo. El administrador de servicios inicia el servicio en un hilo separado y no usa la rutina Principal de la misma manera que una aplicación. –

+0

El OP utiliza un 'System.Timers.Timer' (que aún se ejecuta en un MTA). Sin embargo, todavía no entiendo, ¿por qué STAThread no debería funcionar en un servicio? –

0

Este informa que está usando STA.Se basa en la sugerencia de Will y 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 
    } 
} 
Cuestiones relacionadas