2011-01-09 3 views
3

매 x 분마다 깨어나서 작동하는 x 개의 스레드를 만드는 Windows 서비스를 만들고 싶습니다.C# 4.0에서 상수 실행 스레드가있는 Windows 서비스에 가장 적합한 솔루션

필자는 작업 스케줄링 또는 병렬 프레임 워크가 상수가 아닌 시작, 완료 및 완료 작업에 가장 적합하므로이 유형의 작업에 적합하지 않다고 생각합니다.

이 접근법을 위해 스레드 풀을 활용할 것인가, 아니면 좋은 해결책에 대한 조언이있는 사람이 있습니까?

답변

3

정말로 하나의 스레드 만 필요한 것 같습니다.

다음은 정확히 이런 종류의 작업을 위해 만든 도우미 클래스입니다.

tasks.Shutdown(); 

(또는, backgroundThread와 Start 전화 :

var tasks = new MyPeriodicTasks(); 
tasks.Start(); 

그리고 서비스를 종료 중 : 작업을 시작하려면 다음

class MyPeriodicTasks : PeriodicMultiple 
{ 
    // The first task will start 30 seconds after this class is instantiated and started: 
    protected override TimeSpan FirstInterval { get { return TimeSpan.FromSeconds(30); } } 

    public MyPeriodicTasks() 
    { 
     Tasks = new[] { 
      new Task { Action = task1, MinInterval = TimeSpan.FromMinutes(5) }, 
      new Task { Action = task2, MinInterval = TimeSpan.FromMinutes(15) }, 
     }; 
    } 

    private void task1() { /* code that gets executed once every 5 minutes */ } 
    private void task2() { /* code that gets executed once every 15 minutes */ } 
} 

: 여기 당신이 그것을 사용하는 방법은 사실, 다음 당신은 Shutdown을 호출 할 필요가 없지만, 작업은 무언가를하는 도중 종료 될 수 있습니다.)

여기에 실제 코드입니다 :

/// <summary> 
/// Encapsulates a class performing a certain activity periodically, which can be initiated once 
/// and then permanently shut down, but not paused/resumed. The class owns its own separate 
/// thread, and manages this thread all by itself. The periodic task is executed on this thread. 
/// <para>The chief differences to <see cref="System.Threading.Timer"/> are as follows. This 
/// class will never issue overlapping activities, even if an activity takes much longer than the interval; 
/// the interval is between the end of the previous occurrence of the activity and the start of the next. 
/// The activity is executed on a foreground thread (by default), and thus will complete once started, 
/// unless a catastrophic abort occurs. When shutting down the activity, it's possible to wait until the 
/// last occurrence, if any, has completed fully.</para> 
/// </summary> 
public abstract class Periodic 
{ 
    private Thread _thread; 
    private CancellationTokenSource _cancellation; 
    private ManualResetEvent _exited; 

    /// <summary> 
    /// Override to indicate how long to wait between the call to <see cref="Start"/> and the first occurrence 
    /// of the periodic activity. 
    /// </summary> 
    protected abstract TimeSpan FirstInterval { get; } 

    /// <summary> 
    /// Override to indicate how long to wait between second and subsequent occurrences of the periodic activity. 
    /// </summary> 
    protected abstract TimeSpan SubsequentInterval { get; } 

    /// <summary> 
    /// Override with a method that performs the desired periodic activity. If this method throws an exception 
    /// the thread will terminate, but the <see cref="LastActivity"/> will occur nevertheless. 
    /// </summary> 
    protected abstract void PeriodicActivity(); 

    /// <summary> 
    /// Override with a method that performs an activity on the same thread as <see cref="PeriodicActivity"/> during 
    /// shutdown, just before signalling that the shutdown is complete. The default implementation of this method 
    /// does nothing. This method is guaranteed to be called during a shutdown, even if the shutdown is due to an 
    /// exception propagating outside of <see cref="PeriodicActivity"/>. 
    /// </summary> 
    protected virtual void LastActivity() { } 

    /// <summary> 
    /// Returns false before the first call to <see cref="Start"/> and after the first call to <see cref="Shutdown"/>; 
    /// true between them. 
    /// </summary> 
    public bool IsRunning { get { return _cancellation != null && !_cancellation.IsCancellationRequested; } } 

    /// <summary> 
    /// Schedules the periodic activity to start occurring. This method may only be called once. 
    /// </summary> 
    /// <param name="backgroundThread">By default (false) the class will use a foreground thread, preventing application shutdown until the thread has terminated. If true, a background thread will be created instead.</param> 
    public virtual void Start(bool backgroundThread = false) 
    { 
     if (_thread != null) 
      throw new InvalidOperationException(string.Format("\"Start\" called multiple times ({0})", GetType().Name)); 

     _exited = new ManualResetEvent(false); 
     _cancellation = new CancellationTokenSource(); 
     _thread = new Thread(threadProc) { IsBackground = backgroundThread }; 
     _thread.Start(); 
    } 

    private volatile bool _periodicActivityRunning = false; 

    /// <summary> 
    /// Causes the periodic activity to stop occurring. If called while the activity is being performed, 
    /// will wait until the activity has completed before returning. Ensures that <see cref="IsRunning"/> 
    /// is false once this method returns. 
    /// </summary> 
    public virtual bool Shutdown(bool waitForExit) 
    { 
     if (waitForExit && _periodicActivityRunning && Thread.CurrentThread.ManagedThreadId == _thread.ManagedThreadId) 
      throw new InvalidOperationException("Cannot call Shutdown(true) from within PeriodicActivity() on the same thread (this would cause a deadlock)."); 
     if (_cancellation == null || _cancellation.IsCancellationRequested) 
      return false; 
     _cancellation.Cancel(); 
     if (waitForExit) 
      _exited.WaitOne(); 
     return true; 
    } 

    private void threadProc() 
    { 
     try 
     { 
      _cancellation.Token.WaitHandle.WaitOne(FirstInterval); 
      while (!_cancellation.IsCancellationRequested) 
      { 
       _periodicActivityRunning = true; 
       PeriodicActivity(); 
       _periodicActivityRunning = false; 
       _cancellation.Token.WaitHandle.WaitOne(SubsequentInterval); 
      } 
     } 
     finally 
     { 
      try { LastActivity(); } 
      finally { _exited.Set(); } 
     } 
    } 
} 

/// <summary> 
/// <para>Encapsulates a class performing multiple related yet independent tasks on the same thread 
/// at a certain minimum interval each. Schedules the activity that is the most late at every opportunity, 
/// but will never execute more than one activity at a time (as they all share the same thread).</para> 
/// </summary> 
public abstract class PeriodicMultiple : Periodic 
{ 
    /// <summary> 
    /// Used to define the activities to be executed periodically. 
    /// </summary> 
    protected sealed class Task 
    { 
     /// <summary>The activity to be performed.</summary> 
     public Action Action; 
     /// <summary>The mimimum interval at which this activity should be repeated. May be delayed arbitrarily though.</summary> 
     public TimeSpan MinInterval; 
     /// <summary>Stores the last time this activity was executed.</summary> 
     public DateTime LastExecuted; 
     /// <summary>Calculates by how much this activity has been delayed. Is used internally to pick the next activity to run. Returns negative values for activities that aren't due yet.</summary> 
     public TimeSpan DelayedBy() 
     { 
      if (LastExecuted == default(DateTime)) 
       return TimeSpan.FromDays(1000) - MinInterval; // to run shortest interval first when none of the tasks have ever executed 
      else 
       return (DateTime.UtcNow - LastExecuted) - MinInterval; 
     } 
    } 

    /// <summary>If desired, override to provide a custom interval at which the scheduler 
    /// should re-check whether any activity is due to start. Defaults to 1 second.</summary> 
    protected override TimeSpan SubsequentInterval { get { return TimeSpan.FromSeconds(1); } } 

    /// <summary>Initialise this with the list of activities to be executed.</summary> 
    protected IList<Task> Tasks; 

    /// <summary>For internal use.</summary> 
    protected sealed override void PeriodicActivity() 
    { 
     TimeSpan maxDelay = TimeSpan.MinValue; 
     Task maxDelayTask = null; 

     foreach (var task in Tasks) 
     { 
      var delayedBy = task.DelayedBy(); 
      if (maxDelay < delayedBy && delayedBy > TimeSpan.Zero) 
      { 
       maxDelay = delayedBy; 
       maxDelayTask = task; 
      } 
     } 

     if (maxDelayTask != null) 
     { 
      maxDelayTask.LastExecuted = DateTime.UtcNow; 
      maxDelayTask.Action(); 
     } 
    } 
} 

스레드가 시간 수면의 대부분을 소비하지만 작업으로 인해 있는지 확인하기 위해 1 초마다 일어나 않습니다. 이 1 초 간격은 15 분 간격으로 너무 짧을 수 있으므로 대신 30 초 (예 : SubsequentInterval)로 줄이십시오.

희망이 있습니다. 유용합니다.

+0

(이것은'Threading.Timer' 방법과 다른 방법을 이해하는'Periodic'의

참조)

는 여기에 몇 가지 예제 코드입니다. –

+0

아마도 MS에서 작업 병렬 라이브러리를 사용하면됩니다. – Ryan

+1

이 장기 실행 태스크에 태스크 라이브러리가 가장 적합하다고 생각하지 않습니다. – dagda1

1

글쎄, 나는 당신의 문제가 생산자 소비자 디자인 패턴으로 해결 될 것 같다는 것을 믿습니다.

생산자는 단일 주 스레드가되고 다른 모든 스레드는 소비자 스레드가됩니다. 내 생각에 스레드 풀을 사용하는 것보다 독립적 인 스레드를 사용하는 것이 가장 좋습니다.

예 :

private Thread Worker; 
public Consumer() 
{ 
    Worker = new Thread(ProcessMethod); 
} 

지금 processmethod에 당신은 당신이해야 할 일을. 원하는만큼의 소비자를 만듭니다.

3

x 스레드가 의도적으로 y 분 동안 아무 작업도하지 못하게 할 때 x 스레드를 시작하는 것은 거의 의미가 없습니다. 그냥 하나의 thread do x jobs가 있습니다. 작업을 완료하는 데 x 배의 시간이 걸리지 만 실제로는 y 분이 소요되지 않는 한 전혀 문제가되지 않습니다.

다른 이점은 서비스가 시스템의 응답성에 쉽게 영향을 줄 수없고 다른 코어가 계속 사용할 수 있다는 것입니다. 그리고 코드가 구현 및 디버그하기가 쉬워졌습니다.

System.Threading.Thread 타이머를 사용하여 작업을 활성화하십시오. 콜백은 스레드 풀 스레드에서 실행됩니다. 서비스를 시작하고 중지하는 것은 쉽습니다. 단지 타이머를 활성화/비활성화하십시오.

1

x 분 후에 끊임없이 실행 한 다음 깨우려면 정말 필요한 스레드가 있습니까? 난 당신을 위해 작업을 실행하는 Quartz.NET 같은 기존의 스케줄러 라이브러리를 사용하는 것이 좋습니다 생각하실 수 있습니다.

1

두 가지 제안이 있습니다. 먼저 서비스를 구축하려면 TopShelf을 확인하십시오. 그것은 Windows 서비스 설정의 모든 고통을 제거합니다.

둘째, Observable 클래스를 사용하여 타이머 관련 코드 또는 Quartz (구성 할 고통!)를 작성하지 않고도 타이머를 만들 수 있습니다.

public class MyService 
{ 
    private IDisposable Timer; 

    public void Start() 
    { 
     Timer = ObservableHelpers 
      .CreateMinutePulse(15)  // check every 15 seconds if it's a new minute 
      .Subscribe(i => DoSomething()); 
    } 

    public void Stop() 
    { 
     if(Timer != null) 
     { 
      Timer.Dispose(); 
      Timer = null; 
     } 
    } 

    public void DoSomething() 
    { 
     // do your thing here 
    } 
} 

public static class ObservableHelpers 
{ 
    /// <summary> 
    ///  Returns an observable that pulses every minute with the specified resolution. 
    ///  The pulse occurs within the amount of time specified by the resolution (in seconds.) 
    ///  Higher resolution (i.e. lower specified number of seconds) may affect execution speed. 
    /// </summary> 
    /// <returns></returns> 
    public static IObservable<int> CreateMinutePulse(int resolution) 
    { 
     return Observable 
      .Interval(TimeSpan.FromSeconds(resolution.SetWithinRange(1, 59))) 
      .Select(i => DateTime.Now.Minute) 
      .DistinctUntilChanged(); 
    } 
} 
+0

나는 시도했다. 그러나 Observable은 존재하지 않는다고 말한다? – Mennan

+0

@Mennan Reactive Extension dll이 필요합니다. http://msdn.microsoft.com/en-us/data/gg577609.aspx – Ryan

관련 문제