如何防止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");
}
}
IIS可以/将在一个工作进程(w3wp(下托管多个AppDomain。这些AppDomain不能/不应该/不应该真正地相互交流。IIS有责任管理它们。
我怀疑发生的事情是你加载了多个AppDomain。
也就是说。。。只是为了百分之百的确定。。。计时器正在全局.asax中的Application_Start下启动,对吗?这将在每个AppDomain中执行一次(而不是如其名称所示的每个HttpApplication(。
您可以使用ApplicationManager's GetRunningApplications()
和getGetAppDomain(string id)
方法来检查有多少应用程序域正在为您的进程运行。
理论上,你也可以在那里进行一些应用程序域间的通信,以确保你的流程只启动一次。。。但我强烈建议不要这样做。一般来说,依赖web应用程序的调度是不明智的(因为你的代码不知道IIS是如何管理你的应用程序生存期的(。
首选/推荐的日程安排方法是通过Windows服务。