.NET Windows Service необходимо использовать STAThread

Я создал службу Windows, которая будет вызывать некоторые COM-компоненты, поэтому я пометил [STAThread] на главную функцию. Однако, когда срабатывает таймер, он сообщает, что MTA и COM-вызовы терпят неудачу. Как я могу это исправить?

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

Ответы

Ответ 1

Службы выполняются системой хостинга служб Windows, которая работает с использованием потоков MTA. Вы не можете контролировать это. Вы должны создать новый Thread и установить его ApartmentState в STA, и сделайте свою работу над этой нитью.

Здесь класс, который расширяет ServiceBase, который делает это:

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

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

В такой ситуации я бы создал определенное количество потоков STA (может быть, 2x количество ядер, чтобы начать с), которые отслеживают потокобезопасную очередь для рабочих элементов. Событие отметки таймера будет отвечать за загрузку этой очереди с выполнением работы.

Все зависит от того, что вы на самом деле делаете каждые десять секунд, независимо от того, должно ли оно быть завершено в следующий раз, когда отметит таймер, что вы должны делать в этой ситуации и т.д. и т.д.

Ответ 2

Настройка атрибута STAThread не будет работать на службе. Это не обрабатывается так же, как приложение, поэтому это будет проигнорировано.

Моя рекомендация заключалась бы в том, чтобы вручную создать отдельный поток для вашего сервиса, установить его состояние квартиры и переместить все в него. Таким образом, вы можете правильно установить поток на STA.

Однако здесь будет еще одна проблема - вам придется переработать то, как работает ваш сервис. Вы не можете просто использовать System.Threading.Timer экземпляр для синхронизации - он работает на отдельном потоке, который не будет STA. Когда начнется его прошедшее событие, вы будете работать с другим потоком, отличным от STA.

Вместо выполнения вашей работы в событии таймера вы, вероятно, захотите сделать свою основную работу в потоке, который вы создаете явно. У вас может быть событие reset в этом потоке, который блокирует, и ваш таймер "устанавливает" его, чтобы ваша логика запускалась в потоке STA.

Ответ 3

Это не может работать в службе, поток, который вызывает ваш метод Main(), уже был запущен диспетчером служб. Вам необходимо создать отдельный поток, который инициализируется Thread.SetApartmentState() и накидывает контур сообщения.

Ответ 4

Глядя на аналогичный пример: http://www.aspfree.com/c/a/C-Sharp/Creating-a-Windows-Service-with-C-Sharp-introduction/1/

Что делать, если ваш основной...

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

и переместите ваш таймер в начало/конец событий...

 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

Сообщается, что он использует STA. Он основан на предположении Уилла и 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
    }
}