如何防止Global.asax中的计时器加倍

本文关键字:计时器 何防止 Global asax | 更新日期: 2023-09-27 18:16:17

描述:

在C#ASP.Net web应用程序上,我们实现了一些定时器来定期运行后台任务。其中一个计时器似乎偶尔会"加倍",或者更罕见的是"加倍"。

计时器设置为每分钟运行一次,并且在一段时间内似乎可以正常运行。然而,最终,似乎有第二个计时器启动,并在同一时间间隔内第二次调用定时进程。我甚至见过一个案例,我们有三个进程在运行。

由于这个进程锁定了一些数据库记录,而让第二个(或第三个(进程做同样的事情会导致数据库连接出现死锁或超时错误,因此我们实现了一种机制,一次只允许一个线程执行进程代码的数据库关键部分。当进程运行时间超过一分钟时,此机制会成功阻止由其自身计时器触发的下一次运行。但是,如果进程被第二个(或第三个(计时器触发,线程锁定就会失败。

在我们的日志中,我输出进程ID和托管线程ID,这让我可以看到哪个线程正在启动、完成或出错。奇怪的是,无论哪个计时器实例启动了进程,进程ID都是相同的。

var processID = System.Diagnostics.Process.GetCurrentProcess().Id;
var thread = System.Threading.Thread.CurrentThread.ManagedThreadId;

如何防止计时器出现多个实例

我们有一个网络农场,在负载均衡器后面有2台服务器。我已经得到保证,网络花园设置为每个服务器上只允许一个应用程序池实例。web.config设置指定哪个服务器将运行定时进程。另一台服务器将不会加载计时器。

相关代码:

关于Global.asax.cs

protected static WebTaskScheduler PersonGroupUpdateScheduler
{
    get;
    private set;
}
protected void StartSchedulers()
{
    using (var logger = new LogManager())
    {
        // ... other timers configured in similar fashion ...
        if (AppSetting.ContinuousPersonGroupUpdates)
        {
            // clear out-of-date person-group-updater lock
            logger.AppData.Remove("PersonGroupUpdater"); // database record to prevent interference with another process outside the web application.
            var currentServer = System.Windows.Forms.SystemInformation.ComputerName;
            if (currentServer.EqualsIngoreCase(AppSetting.ContinuousPersonGroupUpdateServer))
            {
                PersonGroupUpdateScheduler = new WebTaskScheduler() {
                    AutoReset = true,
                    Enabled = true,
                    Interval = AppSetting.ContinuousPersonGroupUpdateInterval.TotalMilliseconds,
                    SynchronizingObject = null,
                };
                PersonGroupUpdateScheduler.Elapsed += new ElapsedEventHandler(DistributePersonGroupProcessing);
                PersonGroupUpdateScheduler.Start();
                HostingEnvironment.RegisterObject(PersonGroupUpdateScheduler);
                logger.Save(Log.Types.Info, "Starting Continuous Person-Group Updating Timer.", "Web");
            }
            else
            {
                logger.Save(Log.Types.Info, string.Format("Person-Group Updating set to run on server {0}.", AppSetting.ContinuousPersonGroupUpdateServer), "Web");
            }
        }
        else
        {
            logger.Save(Log.Types.Info, "Person-Group Updating is turned off.", "Web");
        }
    }
}
private void DistributePersonGroupProcessing(object state, ElapsedEventArgs eventArgs)
{
    // to start with a clean connection, create a new data context (part of default constructor)
    // with each call.
    using (var groupUpdater = new GroupManager())
    {
        groupUpdater.HttpContext = HttpContext.Current;
        groupUpdater.ContinuousGroupUpdate(state, eventArgs);
    }
}

在一个单独的文件中,我们有一个WebTaskScheduler类,它只包装System.Timers.Timer并实现IRegisteredObject接口,以便IIS在关闭时将触发的进程识别为需要处理的事情。

public class WebTaskScheduler : Timer, IRegisteredObject
{
    private Action _action = null;
    public Action Action
    {
        get
        {
            return _action;
        }
        set
        {
            _action = value;
        }
    }
    private readonly WebTaskHost _webTaskHost = new WebTaskHost();
    public WebTaskScheduler()
    {
    }
    public void Stop(bool immediate)
    {
        this.Stop();
        _action = null;
    }
}

最后,锁定机制的关键部分代码。

public void ContinuousGroupUpdate(object state, System.Timers.ElapsedEventArgs eventArgs)
{
    var pgUpdateLock = PersonGroupUpdaterLock.Instance;
    try
    {
        if (0 == Interlocked.Exchange(ref pgUpdateLock.LockCounter, 1))
        {
            if (LogManager.AppData["GroupImporter"] == "Running")
            {
                Interlocked.Exchange(ref pgUpdateLock.LockCounter, 0);
                LogManager.Save(Log.Types.Info, string.Format("Group Import is running, exiting Person-Group Updater.  Person-Group Update Signaled at {0:HH:mm:ss.fff}.", eventArgs.SignalTime), "Person-Group Updater");
                return;
            }
            try
            {
                LogManager.Save(Log.Types.Info, string.Format("Continuous Person-Group Update is Starting.  Person-Group Update Signaled at {0:HH:mm:ss.fff}.", eventArgs.SignalTime), "Person-Group Updater");
                LogManager.AppData["PersonGroupUpdater"] = "Running";
                // ... prep work is done here ...
                try
                {
                    // ... real work is done here ...
                    LogManager.Save(Log.Types.Info, "Continuous Person-Group Update is Complete", "Person-Group Updater");
                }
                catch (Exception ex)
                {
                    ex.Data["Continuous Person-Group Update Activity"] = "Processing Groups";
                    ex.Data["Current Record when failure occurred"] = currentGroup ?? string.Empty;
                    LogManager.Save(Log.Types.Error, ex, "Person-Group Updater");
                }
            }
            catch (Exception ex)
            {
                LogManager.Save(Log.Types.Error, ex, "Person-Group Updater");
            }
            finally
            {
                Interlocked.Exchange(ref pgUpdateLock.LockCounter, 0);
                LogManager.AppData.Remove("PersonGroupUpdater");
            }
        }
        else
        {
            // exit if another thread is already running this method
            LogManager.Save(Log.Types.Info, string.Format("Continuous Person-Group Update is already running, exiting Person-Group Updater.  Person-Group Update Signaled at {0:HH:mm:ss.fff}.", eventArgs.SignalTime), "Person-Group Updater");
        }
    }
    catch (Exception ex)
    {
        Interlocked.Exchange(ref pgUpdateLock.LockCounter, 0);
        LogManager.Save(Log.Types.Error, ex, "Person-Group Updater");
    }
}

如何防止Global.asax中的计时器加倍

IIS可以/将在一个工作进程(w3wp(下托管多个AppDomain。这些AppDomain不能/不应该/不应该真正地相互交流。IIS有责任管理它们。

我怀疑发生的事情是你加载了多个AppDomain。

也就是说。。。只是为了百分之百的确定。。。计时器正在全局.asax中的Application_Start下启动,对吗?这将在每个AppDomain中执行一次(而不是如其名称所示的每个HttpApplication(。

您可以使用ApplicationManager's GetRunningApplications()和getGetAppDomain(string id)方法来检查有多少应用程序域正在为您的进程运行。

理论上,你也可以在那里进行一些应用程序域间的通信,以确保你的流程只启动一次。。。但我强烈建议不要这样做。一般来说,依赖web应用程序的调度是不明智的(因为你的代码不知道IIS是如何管理你的应用程序生存期的(。

首选/推荐的日程安排方法是通过Windows服务。