c# - 在 C# 4.0 中具有持续运行线程的 Windows 服务的最佳解决方案

标签 c# .net c#-4.0

我想创建一个 Windows 服务,它将创建 x 个线程,每 x 分钟唤醒一次并执行一些工作。

我认为任务调度或并行框架不适合这种类型的工作,因为它最适合开始、完成和结束的工作,而不是一成不变的工作。

我应该考虑使用线程池来实现这种方法,还是有人对好的解决方案有任何建议?

最佳答案

真的,听起来您只需要一个线程。

这是我为这类事情创建的帮助程序类。以下是您如何使用它:

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

然后,开始任务:

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

在服务关闭期间:

tasks.Shutdown();

(或者,使用 backgroundThread: true 调用 Start,这样您就不需要调用 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)。

希望有用!

关于c# - 在 C# 4.0 中具有持续运行线程的 Windows 服务的最佳解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4638261/

相关文章:

c# - 换声卡播放MP3

c# - OleDb - 关键字 'DEFAULT' 附近的语法不正确

c# - 通过 C# 更新 Azure AD 应用程序

c# - 你如何在 Nhibernate 中进行版本控制?

c#-4.0 - 有什么替代 C# 4.0 中极其昂贵的 Enum.IsDefined() 的方法?

c# - 在 C# 中使用通用存储库时连接多个表

c# - 加载两个版本的程序集时,Visual Studio 2015 无法评估变量

c# - XmlElement 到字符串的转换

string - 如何在 C# 中优化这个 UserAgent 解析器 for 循环?

asp.net - 子域 url 重定向到主 url