开发者

MVC code to see whether or not a Controller is still in use

In my MVC application, I have a controller which creates an instance of a Model object. Within the Model, I have a timer which executes a function every 700ms. When I run the controller within my website, I continue to see the timer running even aft开发者_开发问答er I close the browser window. (The timer starts/stops a serial port connection, and I have a light that indicates when there is communication).

The communication actually never stops until I restart IIS entirely.

Is there some way in which I can check whether the controller is still in use, and thereby stop the timer if the controller is no longer active?

Here is the model code:

public CheckPulses(int interval) {
    CheckPulsesTimer(interval);   
}

public void CheckPulsesTimer(int interval) {
    Timer timer = new Timer();
    timer.Enabled = true;
    timer.AutoReset = true;
    timer.Interval = interval;
    timer.Elapsed += new ElapsedEventHandler(GetPulses);
}

And here is the Controller code:

public ActionResult Index() {
    CheckPulses Pulses = new CheckPulses(700);
    return View();
}

I thought perhaps I should just add something to the GetPulses event, which happens every time the timer interval expires, and check there whether the controller is still in use. If it isn't, stop the timer. However, I'm not sure how to write that check. Can anyone point me in the right direction?


I think the core issue here is in your understanding of what a controller being "in use" is.

It seems as if you are imagining the following:

  1. The user types in your URL and the browser connects to the server.
  2. The server routes the request to an MVC Controller, which keeps a connection to the browser, and responds to each subsequent request using this connection.
  3. The user closes their browser, or navigates away - and the Controller closes down the connection, and disposes.

This is not, however, what actually happens. Web traffic is generally a series of isolated, individual request/response pairings. There is no ongoing connection between the browser and the server.

The more correct sequence is something like the following:

  1. The user types in your URL, and the browser connects to your server on port 80 (or 443 for https), passing in a valid HTTP request, containing a URL, HTTP verb, and other info.
  2. Your server uses the HTTP request's information to route the request to a specific piece of executable code (in this case, your Controller's specific Action method).
  3. In order to execute, the server creates an Instance of your Controller, then fires it's Action method, using the HTTP request's information as parameters.
  4. Your Controller code executes, and an HTTP response message is sent back to the browser.
  5. The Controller is .Dispose()'d by the ControllerFactory.

The issue you're experiencing is not caused by your Controller being "active" - because that's not something Controllers do. You are, instead, creating a Timer within your Model - and the Model, therefore, continues to live on in memory, unable to be killed by GarbageCollection. See this answer for more details.

In essence, think of it like this:

Your Controller is acting as a factory. When you call its Index method, it's equivalent to telling the factory "produce 1 Model". Once the Model rolls out of the factory, the factory is free to shut down, turn off the lights, and send all the workers home (the Controller is .Dispose()'d).

The Model, however, lives on in memory. In most cases, the Model should probably die off due to GarbageCollection once it goes out of scope - but because you have an active Timer inside of it, something is preventing that process from executing - and so the Model continues to live in your server's memory, happily firing its code again and again on each timer expiration.

Note that this has nothing to do with whether or not the user is still on your web-site, or whether their browser is still open, etc. It has to do with the nature of variable scoping and GarbageCollection on your server. You have now started a process which will continue until told to stop.

A potential solution:

In order to fix this, you might consider the following:

  1. Create your Timer inside of a Singleton object. Something like:

    public static class ConnectionManager {
        private static bool initialized = false;
        private static DateTime lastStartedAt = DateTime.Now;
    
        private static Timer timer = null;
    
        private static void Initialize() {
            if (timer == null) {
                timer = new Timer() {
                    AutoReset = true,
                };
                timer.Elapsed += new ElapsedEventHandler(GetPulses);
            }
        }
    
        public static void Start(int interval) {
            lastStartedAt = DateTime.Now;
            Initialize(); // Create a timer if necessary.
            timer.Enabled = true;
            timer.Interval = interval;
        }
        public static void Stop() {
            timer.Enabled = false;
        }
    }
    
  2. Change your Controller to something like this:

    public ActionResult Index() {
        ConnectionManager.Start(700);
        return View();
    }
    
  3. When you handle the expiration of the Timer event, check to see how long ago the lastStartedAt event occurred. If it is more than X minutes, do not process, and fire ConnectionManager.Stop().

This will give you the ability to keep your serial activity polling on a rolling expiration. In other words - each time a user requests your Index page, you will refresh the timer, and ensure that you are listening. After X minutes, though, if no users have made a request - you can simply shut down the timer and the associated port.


You are likely not stopping or disposing the Timer. This will cause it to not be garbage collected and stay active for the duration the application stays running.

You should stop and dispose of the Timer within the Controller's Dispose method.


You have several options:

  1. Override Dispose method on the controller and dispose your model with calling Pulses.Dispose()
  2. Override OnActionExecuted method on the controller and do disposing
  3. Create custom action filter and implement OnActionExecuted method and assign it to controller or action.(You can place reference to your model into TempData and then in your ActionFilter check if filterContext.Controller.TempData["MyReferenceToModel"] != null, then use it to dispose timer)
  4. Implement IDisposable interface in your Model and use it inside using statement

Hope this helps, Dima


You can stop the Timer with JQuery+ MVC.

Jquery can respond to the unload event of the page like so:

$(window).unload(function() {
  alert('Handler for .unload() called.');
});

You can make ajax calls to your controllers. We also need to create a property to access the timer. I'll assume you called your Timer Property MyTimer

Timer MyTimer { get; set; }

So you need to create a controller that returns void and calls the Stop() method of your Timer. [HttpPost] public void KillTimer() { MyTimer.Stop(); }

Now if you add an Ajax call to your unload event we created earlier, then we should be able to kill the timer whenever the page closes.

$.ajax(
{
   type: "POST",
   url: "/Home/Index",
   ,
   success: function(result)
    {
        alert("Timer Closed");
    },                
   error : function(req, status, error)
   {
        alert("Sorry! We could not receive your feedback at this time.");   
   }
});

The success and error functions are not necessary. I included them only for demonstration. I also guessed the name of your controller to be Home. You will need to update the URL if it is something different. You could do this with pure javascript. JQuery helps to save you from writing lots of tedious browser compatibility code. (I hear opera is a pain with the unload part)

To address the issue of potential browser crashes and unexpected events you can wire up your controller to check if the timer has exceeded a max time limit.

Good Luck !

Sources :

JQuery Unload()

Invoking ASP.NET MVC Actions from JavaScript using jQuery

JQuery Ajax()

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜