.NET Windows Service необходимо использовать STAThread - PullRequest
14 голосов
/ 04 января 2010

Я создал службу Windows, которая будет вызывать некоторые компоненты COM, поэтому я пометил [STAThread] для функции Main. Однако, когда таймер срабатывает, он сообщает о 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
    }
}

Ответы [ 5 ]

24 голосов
/ 04 января 2010

Службы запускаются системой размещения служб Windows, которая работает с использованием потоков MTA. Вы не можете контролировать это. Вы должны создать новую тему и , установить для ее 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 (возможно, в два раза больше ядер, с которых нужно начинать), которые отслеживают потокобезопасную очередь для рабочих элементов. Событие отметки таймера будет отвечать за загрузку этой очереди необходимой работой.

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

5 голосов
/ 04 января 2010

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

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

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

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

4 голосов
/ 04 января 2010

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

0 голосов
/ 05 января 2010

Сообщает, что использует STA. Он основан на предложении Уилла и http://en.csharp -online.net / Создание_a_.NET_Windows_Service% E2% 80% 94Al альтернатива_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
    }
}
0 голосов
/ 04 января 2010

Глядя на похожий пример: 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);
    }
...