0

I have several Machine classes which have state whether they are online/offline and DateTime EndsAt when they will turn offline if they are online. They are (mapped?) to database using EF. When i turn them on i pass amount of seconds for them to stay online and create System.Threading.Timer to change its state back to offline when the time comes (EndsAt == DateTime.Now). Turning them on works fine, however they don't turn off - turnoff() is never called. And on top of that if it would be called and object would change its own variables will they be saved by 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);
    }
Edgar.A
  • 1,293
  • 3
  • 13
  • 26
  • Pretty sure entity framework doesn't hold the object in memory after you flush it back to the database, and objects don't run out of the database. not sure how this can work unless you store machines somewhere else other than the database context. .. – Ron Beyer Jul 18 '15 at 16:03

2 Answers2

2

The problem comes not from Entity Framework but ASP.NET.

The best way I can describe it is imagine your page request in ASP.NET is a console application, every new request the application starts up, does the request and responds to the user, waits a tiny bit for another request to come in then exits the Main() function.

If you created a Timer in that kind of application once the "tiny bit" runs out and the Main() returns your timer will not be running anymore and the thing you where waiting to happen will never happen. IIS does this exact process but it does it with AppDomain recycling, if no requests come in it will shut down the AppDomain and will kill your timer.

There two ways I know of to handle this problem:

The first way is you need to make a 2nd application that runs as a windows service outside of IIS that is always running, it will be what holds the timer. When you want to run any kind of long running operation that will outlive a page request you use WCF or some other technology for your web app to communicate with the service to start up the timer, when the timer is done either the service executes whatever operation you wanted done.

The second way to do it is you save the timer request in a database then in the background before every request you check the database of events and see if any need to be executed. There are libraries like hangfire that make this process easy, they also have tricks to keep the app domain alive longer or wake it back up if it shuts down (often they use two websites that talk to each other each keeping the other one alive).

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
0

Even though this specific question has been answered, here's some related discussion I hope can be helpful in the case of a timer callback not working.

Import considerations when using Threading.Timer


1.) Timer is subject to garbage collection. Even if active, it may be collected as garbage if it does not haven a reference.

2.) DotNet has many different types of timers, and it's important to use the right kind in the right way because it involves threading. Use Forms.Timer for Forms, Threading.Timer or wrap it in Timers.Timer (debate on thread safety), or Web.UI.Timer with ASP.NET for web page postbacks.

3.) The Callback method is defined when the timer is instantiated and cannot be changed.

Timer Related Tools


1.) You can use Thread.Sleep to release CPU resources and place your thread in a waitsleepjoin state which is essentially stopped.

2.) Sometimes a Task can be used along with or instead of a timer.

3.) Stopwatch can be used in different ways, for example, with an empty loop.

Community
  • 1
  • 1
u8it
  • 3,956
  • 1
  • 20
  • 33
  • I don't see how this answers the OP's question at all. 1a) He keeps a refrence to the timer. 2a) He is doing ASP.NET but not doing a postback so your advice is incorrect there. Also none of the other alternatives you suggest would have solved his issue of the AppDomain unloading. 3a) He does not try to change the callback method. 1b) Thread.Sleep is almost never the correct answer, especially when you are using a timer already. 2b) Tasks will have the same problem with being terminated early. – Scott Chamberlain Aug 31 '15 at 18:41
  • 3b) Stopwatches have nothing to do with Timers, they are for measuring time, not executing code. – Scott Chamberlain Aug 31 '15 at 18:56
  • @Scott Chamberlain, yes, I agree, it doesn't answer the OP's question... I only posted because this question is a top Google return on timers but has very little timer discussion about Timers on the answer board, and I feel that the most helpful questions are those with a little more discussion... so if you think I should delete this that's fine because I almost didn't post it, but I did post it hoping to improve the discussion. – u8it Aug 31 '15 at 19:14
  • No, that reasoning is fair enough. I removed my -1. – Scott Chamberlain Aug 31 '15 at 19:17
  • Thanks, I'm glad for the feedback... also, I'm changing the name of my last section to clarify that I don't necessarily mean "use with a timer" – u8it Aug 31 '15 at 19:29