Timer timer does not trigger a callback
I have several machine classes that say if they are online / offline and DateTime EndsAt when they will be offline if they are online. They are (displayed?) Into the database using EF. When I turn them on, I skip the number of seconds for them to stay online and create a System.Threading.Timer to change their state back to offline when the time comes (EndsAt == DateTime.Now). Enabling them is fine, but they don't turn off - turnoff () is never called. And besides, if it gets called and the object changes its own variables, will they be saved by the entity framework?
public class Machine
{
private Timer timer=null;
[Key]
public int MachineId { get; set; }
public bool Online { get; set; }
public DateTime EndsAt { get; set; }
public void TurnOn(TimeSpan amount)
{
Debug.WriteLine("Turn on reached");
if (!Online)
{
EndsAt = DateTime.Today.Add(amount);
Online = true;
setTimer();
}
}
private void turnOff(object state)
{
Online = false;
Occuppied = false;
Debug.WriteLine("Timer ended!");
}
private void setTimer()
{
Debug.WriteLine("Timer being set");
if (EndsAt.CompareTo(DateTime.Now) == 1)
{
timer = new Timer(new TimerCallback(turnOff));
int msUntilTime = (int)((EndsAt - DateTime.Now).TotalMilliseconds);
timer.Change(msUntilTime, Timeout.Infinite);
}
else
{
Debug.WriteLine("EndsAt is smaller than current date");
}
}
}
Controller method where turnOn () is called
[HttpPost]
public ActionResult TurnOn() {
bool isChanged = false;
if (Request["machineId"] != null && Request["amount"] != null)
{
byte machineId = Convert.ToByte(Request["machineId"].ToString());
int amount = Convert.ToInt32(Request["amount"].ToString());
foreach (var machine in db.Machines.ToList())
{
if (machine.MachineId == machineId)
{
machine.TurnOn(TimeSpan.FromSeconds(amount));
db.Entry(machine).State = EntityState.Modified;
db.SaveChanges();
isChanged = true;
}
}
}
if (isChanged)
return new HttpStatusCodeResult(HttpStatusCode.OK);
else
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
source to share
The problem does not come from Entity Framework, but from ASP.NET.
The best way to describe it is to imagine your page request in ASP.NET is a console application, every new request that starts the application, does the request and responds to the user, waits for a small bit to receive another request, then exits the function Main()
.
If you create a timer in such an application after the "tiny bit" runs out and Main()
your timer returns, it will no longer work, and what you expect will never happen. IIS does this exact process, but it does it by recycling the AppDomain, if no requests come in, it will disable the AppDomain and kill your timer.
There are two ways I know to deal with this problem:
First of all, you need to make a second application that runs as a Windows service outside of IIS that is always running, and that will be what is holding the timer. If you want to start any long operation that goes into a page request, you are using WCF or some other technology for your web application to communicate with the service to start the timer when the timer is running or the service is performing whatever operation you wish to do.
The second way to do this is to store the timer request in the database and then in the background before each request check the event database to see if it needs to be executed. There are libraries like hangfire that make this process easier, they also have tricks to keep the app domain longer or wake it up if it is (often they use two websites that talk to each other while keeping each other alive).
source to share
Although this specific question has been answered, here some related discussions in my opinion might be helpful in case of a broken timer callback.
Importing considerations when using Threading.Timer
1.) The timer is subject to garbage collection. Even if it is active, it can be garbage collected if it does not contain links.
2.) DotNet has many different types of timers and it is important to use the correct type correctly because it includes threads. Use Forms.Timer for Forms, Threading.Timer, or wrap it in Timers.Timer ( thread safety discussion ) or Web.UI.Timer with ASP.NET for Web page postbacks.
3.) The callback method is defined when the timer is created and cannot be changed.
Timer related tools
1.) You can use Thread.Sleep to free up CPU resources and put your thread in a waitsleepjoin state that is essentially stopped.
2.) Sometimes Task can be used with or instead of a timer.
3.) Stopwatch can be used in different ways, for example with an empty loop.
source to share