I have this service which calls Notification() function every second. What this function does is it checks the time difference between lastupdated and DateTime.UtcNow and check if the Countdiff is greater than 1
If the above conditions is true then it checks if the AppGuid is already in the DB if it's not it inserts it and calls DoAction() function which sends a notification. Everything works fine till here.
The problem starts when the AppGuid is already in DB.
What I need is if AppGuid is already in DB then send notification every 15 Min, here I'm struggling with keeping track last time I send notification, I tried to compare last timestamp when it got updated and tried to compare the difference but it keeps sending notifications resulting spamming the pipeline.
I tried to keep elasped time with stopwatch class but the problem it waits for 15 min when app starts initially to send anything.
How do I send notification only once every 15 min?
public class DataGetService : DelegatingHandler, IHostedService
{
private Timer _timer;
Stopwatch stopwatch = new Stopwatch();
public Task StartAsync(CancellationToken cancellationToken)
{
stopwatch.Start();
_timer = new Timer(Heartbeat, null, 1000, 1000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Timer does not have a stop.
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Heartbeat(object state)
{
_ = Notifications(TimeSpan.FromMinutes(15));
}
public async Task Notifications(TimeSpan period)
{
// Result array have: LastUpdated, CountDiff, AppGuid, AppName, Timestamp
foreach (var d in result)
{
var x = _DBcontext.TestEvents.FirstOrDefault(o => o.AppGuid == (Guid)d.AppGuid);
if (DateTime.UtcNow - d.LastUpdated <= TimeSpan.FromMinutes(30) && d.CountDiff > 1)
{
if (x == null)
{
DoAction();
_DBcontext.TestEvents.Add(new TestEvents
{
AppGuid = (Guid)d.AppGuid,
AppName = d.AppName,
Timestamp = DateTime.UtcNow
});
}
else
{ //This is useful if app have crashed or was not able to update in db, it will update the current timestamp.
if (DateTime.UtcNow - x.Timestamp >= TimeSpan.FromMinutes(30))
{
x.Timestamp = DateTime.UtcNow;
}
else
{
if (stopwatch.Elapsed <= period)
return;
DoAction();
x.Timestamp = DateTime.UtcNow;
}
}
}
}
await _DBcontext.SaveChangesAsync();
stopwatch.Restart();
}
}
I'd do it the same way I would lock a user out for 15 minutes if they tried their password too many times: rather than trying to use a date field that tracks when a record was last updated, have a dedicated field for when the next event should be raised. At the time you send the first notification, set it to a future date and only send a new notification when the date falls into the past (whereupon you set the date future again)
By using a "last updated" field you risk another notification if something else about the record changes. If you feel that it's not relevant to put the date in the table concerned, consider having a table just for notification event dates; all it needs is a GUID and a date, and then it can function for any object ID in any table in the database (if the GUIDs are unique) - "no sending information about entity with GUID x until time y" is an easy thing to code for in that case and the eventing system doesnt need to know anything about the entity it is reporting on. You can make the subsystems naively raise events every second if they want to, but the sending of notifications can only happen every X minutes, so all the interim notifications are quenched. This simplifies the system raising the messages too; it can just raise them and not care for the logic of whether they should actually be notified or not
For future answer seekers:
I have added futureTime variable which adds 15 min to the Timestamp from DB.
The interesting thing was when I compare with if(DateTime.UtcNow == futureTime) the condition never became true, reason what I have understood is I call this function every second during the execution somehow system was skipping the time resulting false condition.
To overcome this I took the difference (DateTime.UtcNow - futureTime) and also consider the tolerance of seconds.
if (DateTime.UtcNow - d.LastUpdated <= TimeSpan.FromMinutes(30) && d.CountDiff > 1)
{
if (x == null)
{
DoAction();
_DBcontext.TestEvents.Add(new TestEvents
{
AppGuid = (Guid)item.AppGuid,
AppName = item.AppName,
Timestamp = DateTime.UtcNow
});
}
//This is useful if app have crashed or was not able to update in db, it will update the current timestamp.
else if (DateTime.UtcNow - x.Timestamp >= TimeSpan.FromMinutes(30))
{
x.Timestamp = DateTime.UtcNow;
}
else
{
//This will set the notification date in future
DateTime dbtimestamp = x.Timestamp;
DateTime futureTime = dbtimestamp.AddMinutes(15);
if (((DateTime.UtcNow - futureTime) - TimeSpan.FromSeconds(1)).Duration() < TimeSpan.FromSeconds(1.0))
{
DoAction();
x.Timestamp = DateTime.UtcNow;
}
}
}
Related
I am running hangfire in a single web application, my application is being run on 2 physical servers but hangfire is in 1 database.
At the moment, i am generating a server for each queue, because each queue i need to run 1 worker at a time and they must be in order. I set them up like this
// core
services.AddHangfire(options =>
{
options.SetDataCompatibilityLevel(CompatibilityLevel.Version_170);
options.UseSimpleAssemblyNameTypeSerializer();
options.UseRecommendedSerializerSettings();
options.UseSqlServerStorage(appSettings.Data.DefaultConnection.ConnectionString, storageOptions);
});
// add multiple servers, this way we get to control how many workers are in each queue
services.AddHangfireServer(options =>
{
options.ServerName = "workflow-queue";
options.WorkerCount = 1;
options.Queues = new string[] { "workflow-queue" };
options.SchedulePollingInterval = TimeSpan.FromSeconds(10);
});
services.AddHangfireServer(options =>
{
options.ServerName = "alert-schedule";
options.WorkerCount = 1;
options.Queues = new string[] { "alert-schedule" };
options.SchedulePollingInterval = TimeSpan.FromMinutes(1);
});
services.AddHangfireServer(options =>
{
options.ServerName = string.Format("trigger-schedule");
options.WorkerCount = 1;
options.Queues = new string[] { "trigger-schedule" };
options.SchedulePollingInterval = TimeSpan.FromMinutes(1);
});
services.AddHangfireServer(options =>
{
options.ServerName = "report-schedule";
options.WorkerCount = 1;
options.Queues = new string[] { "report-schedule" };
options.SchedulePollingInterval = TimeSpan.FromMinutes(1);
});
services.AddHangfireServer(options =>
{
options.ServerName = "maintenance";
options.WorkerCount = 5;
options.Queues = new string[] { "maintenance" };
options.SchedulePollingInterval = TimeSpan.FromMinutes(10);
});
My problem is that it is generating multiple queues on the servers, with different ports.
In my code i am then trying to stop jobs from running if they are queued/retrying, but if the job is being run on a different physical server, it is not found and queued again.
Here is the code to check if its running already
public async Task<bool> IsAlreadyQueuedAsync(PerformContext context)
{
var disableJob = false;
var monitoringApi = JobStorage.Current.GetMonitoringApi();
// get the jobId, method and queue using performContext
var jobId = context.BackgroundJob.Id;
var methodInfo = context.BackgroundJob.Job.Method;
var queueAttribute = (QueueAttribute)Attribute.GetCustomAttribute(context.BackgroundJob.Job.Method, typeof(QueueAttribute));
// enqueuedJobs
var enqueuedjobStatesToCheck = new[] { "Processing" };
var enqueuedJobs = monitoringApi.EnqueuedJobs(queueAttribute.Queue, 0, 1000);
var enqueuedJobsAlready = enqueuedJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo) && enqueuedjobStatesToCheck.Contains(e.Value.State));
if (enqueuedJobsAlready > 0)
disableJob = true;
// scheduledJobs
if (!disableJob)
{
// check if there are any scheduledJobs that are processing
var scheduledJobs = monitoringApi.ScheduledJobs(0, 1000);
var scheduledJobsAlready = scheduledJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo));
if (scheduledJobsAlready > 0)
disableJob = true;
}
// failedJobs
if (!disableJob)
{
var failedJobs = monitoringApi.FailedJobs(0, 1000);
var failedJobsAlready = failedJobs.Count(e => e.Key != jobId && e.Value != null && e.Value.Job != null && e.Value.Job.Method.Equals(methodInfo));
if (failedJobsAlready > 0)
disableJob = true;
}
// if runBefore is true, then lets remove the current job running, else it will write a "successful" message in the logs
if (disableJob)
{
// use hangfire delete, for cleanup
BackgroundJob.Delete(jobId);
// create our sqlBuilder to remove the entries altogether including the count
var sqlBuilder = new SqlBuilder()
.DELETE_FROM("Hangfire.[Job]")
.WHERE("[Id] = {0};", jobId);
sqlBuilder.Append("DELETE TOP(1) FROM Hangfire.[Counter] WHERE [Key] = 'stats:deleted' AND [Value] = 1;");
using (var cmd = _context.CreateCommand(sqlBuilder))
await cmd.ExecuteNonQueryAsync();
return true;
}
return false;
}
Each method has something like the following attributes as well
public interface IAlertScheduleService
{
[Hangfire.Queue("alert-schedule")]
[Hangfire.DisableConcurrentExecution(60 * 60 * 5)]
Task RunAllAsync(PerformContext context);
}
Simple implementation of the interface
public class AlertScheduleService : IAlertScheduleService
{
public Task RunAllAsync(PerformContext context)
{
if (IsAlreadyQueuedAsync(context))
return;
// guess it isnt queued, so run it here....
}
}
Here is how i am adding my scheduled jobs
//// our recurring jobs
//// set these to run hourly, so they can play "catch-up" if needed
RecurringJob.AddOrUpdate<IAlertScheduleService>(e => e.RunAllAsync(null), Cron.Hourly(0), queue: "alert-schedule");
Why does this happen? How can i stop it happening?
Somewhat of a blind shot, preventing a job to be queued if a job is already queued in the same queue.
The try-catch logic is quite ugly but I have no better idea right now...
Also, really not sure the lock logic always prevents from having two jobs in EnqueudState, but it should help anyway. Maybe mixing with an IApplyStateFilter.
public class DoNotQueueIfAlreadyQueued : IElectStateFilter
{
public void OnStateElection(ElectStateContext context)
{
if (context.CandidateState is EnqueuedState)
{
EnqueuedState es = context.CandidateState as EnqueuedState;
IDisposable distributedLock = null;
try
{
while (distributedLock == null)
{
try
{
distributedLock = context.Connection.AcquireDistributedLock($"{nameof(DoNotQueueIfAlreadyQueued)}-{es.Queue}", TimeSpan.FromSeconds(1));
}
catch { }
}
var m = context.Storage.GetMonitoringApi();
if (m.EnqueuedCount(es.Queue) > 0)
{
context.CandidateState = new DeletedState();
}
}
finally
{
distributedLock.Dispose();
}
}
}
}
The filter can be declared as in this answer
There seems to be a bug with your currently used hangfire storage implementation:
https://github.com/HangfireIO/Hangfire/issues/1025
The current options are:
Switching to HangFire.LiteDB as commented here: https://github.com/HangfireIO/Hangfire/issues/1025#issuecomment-686433594
Implementing your own logic to enqueue a job, but this would take more effort.
Making your job execution idempotent to avoid side effects in case it's executed multiple times.
In either option, you should still apply DisableConcurrentExecution and make your job execution idempotent as explained below, so i think you can just go with below option:
Applying DisableConcurrentExecution is necessary, but it's not enough as there are no reliable automatic failure detectors in distributed systems. That's the nature of distributed systems, we usually have to rely on timeouts to detect failures, but it's not reliable.
Hangfire is designed to run with at-least-once execution semantics. Explained below:
One of your servers may be executing the job, but it's detected as being failed due to various reasons. For example: your current processing server does not send heartbeats in time due to a temporary network issue or due to temporary high load.
When the current processing server is assumed to be failed (but it's not), the job will be scheduled to another server which causes it to be executed more than once.
The solution should be still applying DisableConcurrentExecution attribute as a best effort to prevent multiple executions of the same job, but the main thing is that you need to make the execution of the job idempotent which does not cause side effects in case it's executed multiple times.
Please refer to some quotes from https://docs.hangfire.io/en/latest/background-processing/throttling.html:
Throttlers apply only to different background jobs, and there’s no
reliable way to prevent multiple executions of the same background job
other than by using transactions in background job method itself.
DisableConcurrentExecution may help a bit by narrowing the safety
violation surface, but it heavily relies on an active connection,
which may be broken (and lock is released) without any notification
for our background job.
As there are no reliable automatic failure detectors in distributed
systems, it is possible that the same job is being processed on
different workers in some corner cases. Unlike OS-based mutexes,
mutexes in this package don’t protect from this behavior so develop
accordingly.
DisableConcurrentExecution filter may reduce the probability of
violation of this safety property, but the only way to guarantee it is
to use transactions or CAS-based operations in our background jobs to
make them idempotent.
You can also refer to this as Hangfire timeouts behavior seems to be dependent on storage as well: https://github.com/HangfireIO/Hangfire/issues/1960#issuecomment-962884011
I had a function which update the database by every second (as continuously data coming by some Network) I wanted to put delay on that updating function.. As it would update database table by every 5 minutes..
Here is my Code
if (ip==StrIp)
{
Task.Delay(300000).ContinueWith(_=>
{ //I'm Using Task.Delay to make delay
var res= from i in dc.Pins //LINQ Query
where i.ip== ip
select i;
for each (var p in res)
{
p.time= System.DateTime.Now,
p.temperature= temp,
.
. //some other values
.
};
datacontext.submitChanges();
});
}
It is working and updating data by every 5 minutes, Now I want that data should update immediately only first time when application start but after that It should update after every 5 minutes.. But Right now my code isn't doing that..
How can I make such delay which ignore the operation first time, but apply on upcoming data iterations..?
Thanks in Advance
You could use a flag to determine whether it is the first time your method is called, e.g.:
private uint _counter = 0;
public YourMethod()
{
if (ip == StrIp)
{
Action<Task> action = _ =>
{
var res = from i in dc.Pins //LINQ Query
where i.ip == ip
select i;
//...
datacontext.submitChanges();
};
if (_counter++ == 0)
action();
else
Task.Delay(300000).ContinueWith(action);
}
}
Extract the inner logic of the task into a function/method (refactoring of VS or R# can to this automatically) and call the new function/method at start and on the interval.
I personally would go into another direction:
Have a in-memory queue that gets filled with data as it comes into your app. Then I would have a thread/task etc. which checks the queue every 5 minutes and updates the database accordingly. Remember to lock the queue for updates (concurrency). The ConcurrentQueue of .Net is one way to do it.
I have a Windows service that spawns different tasks in threads at different intervals. Some happen once a day, some happen every couple minutes (like the one that is giving me an issue).
My goal was to code it so that a new thread will be created for the task, but only if the previous time it was spawned has completed (or it has never been created / first run). There should never be more than one "checkThread" thread running at a time.
I have other areas where I use the same code as below to check if I should spawn the new thread or not, and those all work without issue, but with one of them I get an exception very infrequently:
System.Threading.ThreadStateException: Thread is running or terminated; it cannot restart.
The following are defined outside of the main for{} loop and are used to track what's going on:
const int MINUTES_CHECK = 1;
DateTime lastCheck = DateTime.Now.AddMinutes(-MINUTES_CHECK); //force this to run immediately on first run
Thread checkThread; //used to poll status of thread
const int DIFFERENT_MINUTES_CHECK = 5; //maybe this one only runs every 5 minutes
DateTime different_LastCheck = DateTime.Now.AddMinutes(-DIFFERENT_MINUTES_CHECK); //force this to run immediately on first run
Thread different_checkThread; //used to poll status of thread
This is inside the service's main for{} loop and is how I check if the thread should be created or not. The goal is only to start a new checkThread if it has never been started before, or of the previous one is not still running:
// Worker thread loop
for (; ; )
{
if ((DateTime.Now - lastCheck).TotalMinutes >= MINUTES_CHECK)
{
if (checkThread == null ||
(checkThread.ThreadState != System.Threading.ThreadState.Running &&
checkThread.ThreadState != System.Threading.ThreadState.WaitSleepJoin)
)
{
checkThread = new Thread(DoCheck);
checkThread.Start();
Console.WriteLine("Checking for items...");
lastCheck = DateTime.Now;
}
}
if ((DateTime.Now - different_LastCheck).TotalMinutes >= DIFFERENT_MINUTES_CHECK)
{
if (different_checkThread== null ||
(different_checkThread.ThreadState != System.Threading.ThreadState.Running &&
different_checkThread.ThreadState != System.Threading.ThreadState.WaitSleepJoin)
)
{
different_checkThread= new Thread(DoSomethingElse);
different_checkThread.Start();
Console.WriteLine("Checking for something else...");
different_LastCheck = DateTime.Now;
}
}
//// Run this code once every N milliseconds
var wait_for = new TimeSpan(0, 0, 0, 0, 1000);
if (mStop.WaitOne(wait_for)) return;
}
It works 99.9% of the time, and so far I have been unable to get it to occur in Visual Studio. I have run it for a couple hours in VS with no issue, and even loaded the CPU to 99% and ran if for half an hour or so and still did not get the exception.
I did the above because my suspicion is that it's trying to start the new thread and assign it to "checkThread" before the previous one has finished (although this does seem unlikely since there's really not all that much going on inside "DoCheck" and 1 minute should be more than enough for it to complete).
Am I missing another possible ThreadState, or is there something else going on?
Edit: Exception occurs on checkThread.Start();
This error occurs when multiple attempts (more than 1 thread) are trying to execute the .Start(), you need to sync it.
Are there ANY other places that issue a START()?
Try this:
Add a static _syncRoot:
private static _syncRoot = new object();
Synchronize your start:
if ((DateTime.Now - lastCheck).TotalMinutes >= MINUTES_CHECK)
{
if (checkThread == null ||
(checkThread.ThreadState != System.Threading.ThreadState.Running &&
checkThread.ThreadState != System.Threading.ThreadState.WaitSleepJoin)
)
{
lock( _syncRoot ) {
checkThread = new Thread(DoCheck);
checkThread.Start();
Console.WriteLine("Checking for items...");
lastCheck = DateTime.Now;
}
}
}
TCP server send data to buffer and MainForm receive data from buffer and do some processing
I have a parameter to check the time on lastDataReceive (from TCP) on the buffer.
I also have a paramter to check the time on dataReceiveNow (from buffer) on the MainForm.
Now, I want to calculate the elasped time, if lastDataReceive (from TCP) - dataReceiveNow (from buffer) > 60 seconds, then it will prompt MessageBox.Show(" Connection Time out ");
I have two method on doing it, but I don't know which one gives the best result or actually both are the same?
1st method: Get the elasped time using DateTime format and check the condition of 1 second
private void CheckConnectionTimeOut()
{
if (DateTime.Now.Subtract(receiver.LastDataReceivedTime).TotalSeconds > 60)
{
MessageBox.Show("Connection Out");
}
else
{
// do what ever
}
}
2nd method: Get the elasped time by calculating the elapsedTicks
private void CheckConnectionTimeOut()
{
long dataTimeOut = (long)TimeSpan.FromTicks(receiver.LastDataTick - dataReceiveTickNow).TotalSeconds;
DateTime dt = new DateTime(dataTimeOut);
if (dt.Second > 60)
{
MessageBox.Show("Connection Out");
}
else
{
// do what ever
}
}
Neither of those are recommended, and can actually be dangerous.
Instead, use a Stopwatch class to measure elapsed duration. Reference documentation here.
I have a small MVC website which is for a friends Hair Salon. On this page I have a div which is used to display a number which it takes from a database record. This number is the current number of people sitting in the queue waiting for a haircut.
What I have currently is the ability to logon to an "admin" page and update this number using a form, from say "2" to "5", then change "5" to "6" dependant on how many people are sitting in this queue.
This is a manual operation as it currently stands. Code is below:
=============================
Controller
[HttpPost]
public ActionResult Update(Data data)
{
if (ModelState.IsValid)
{
data.ID = 1; //EF need to know which row to update in the database.
db.Entry(data).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(data);
}
====================================
Model code
{
public class Data
{
public int ID { get; set; }
public string Queue_Number { get; set; }
}
public class DataDBContext : DbContext
{
public DbSet<Data>Queue { get; set; }
}
}
What I would really like to happen is that once you have manually updated the Queue Number from the form on the "admin" page I'd like an automatic count down of 20 minutes (the rough time it takes for the haircut) and then have the Queue Number auto-adjust down by one till it gets to "0".
e.g. We have 5 people in the queue, 20 minutes later it is auto adjusted to 4 people and the web page will auto update / refresh, then 2 more people walk in so we manually adjust it to 6 people in the queue and the timer starts again, each 20 min passes the queue is adjusted by -1 till it gets down to "0". Once it gets to "0" it stays there until we manually add more people to the queue.
I'm afraid I have no idea how to even begin with such a request, or even if it is possible?
I'd be really thankful for any help from the experts here that might be able to "babystep" it for me. Any information I've not provided I'll endeavour to add - I realise I'm not the best at explaining myself :-(
Have you considered Ajax? are you storing the last updated time on manually setting the flag? You can use Ajax request to simultaneously run using jquery Set interval. which will trigger the ajax request every 2 minutes. Find the last time it was updated, if that is passed 20 minutes then remove one from the database, your return would be the new number and jquery can update that number for you.
Quite a simple process actually but need more detail on the underlying data.
Here is how I can see it working from your question
In Controller
public ActionResult ajaxUpdate()
{
//open connection
dbcontext db = new dbcontext();
db.Connection.Open();
// get the last updated record in the database.
var entry = db.Entry.OrderByDecending(m=> m.LastUpdatedDate).FirstOrDefault();
//clean up
db.Connection.Close();
db.Dispose();
//return -1 as error
if(entry == null){
return Json(-1,JsonRequestBehavior.AllowGet);
}
// get current number of people in queue
Int32 numberOfPeople = entry.QueueNumber;
TimeSpan span = DateTime.Now.Subtract(entry.LastUpdatedDate);
if(span.Minutes >= 20){
// if 20 mins have passed assume a person has been completed since manual update
numberOfPeople--;
}
//this returns a number, alternatively you can return a Partial
return Json(numberOfPeople, JsonRequestBehavior.AllowGet);
}
Jquery and Ajax
$(document).ready(function () {
// run function every x minutes
setInterval(function () {
UpdateQueue();
}, 100000);
});
function UpdateQueue() {
$.ajax({
cache: true,
type: 'POST',
url: "/ControllerName/ajaxUpdate",
async: false,
dataType: "json",
success: function (result) {
// on success result will be the number returned
// -1 is error
if (result == -1) {
return;
}
// check the -- didn't return a negative
if (result < 0) {
result = 0;
}
//find your element in the HTML to update
$('#NumberElement').text().replaceWith(result);
}
});
}
You must ensure you include your jquery libraries before you include this code or you will have Jquery not defined.
I have made up for you server side solution with a little bit threading. Hope I am correct on critical sections locks.
It has an advantage that admin of your application does not have to hang on the page to get number of current customers downcounted (like he should with ajax requests).
How it works
On 'number of customers' update it is starting (if necessary) new counting-down thread, which waits (sleeps) for predefined interval and then decreases the number.
public class CustomerAdminService
{
// time in milliseconds it will take to decrease number of waiting customers
const int sleepTime = 10000;
// current number of customers (just for simplicity - you can have it in db or somewhere else)
static int numberOfCustomers;
static Thread updaterThread;
// object lock
static readonly object locker = new Object();
public int GetNumberOfCustomers()
{
return numberOfCustomers;
}
public void UpdateCustomers(int value)
{
lock (locker)
{
if (updaterThread == null)
{
//start new downcounting thread
updaterThread = new Thread(new ThreadStart(UpdateWorker));
updaterThread.Start();
}
SetNumberOfWaitingCustomers(value);
}
}
private void SetNumberOfWaitingCustomers(int value)
{
numberOfCustomers = value;
}
// downcounting thread method
private void UpdateWorker()
{
while (true)
{
// sleep for predefined time
Thread.Sleep(sleepTime);
lock (locker)
{
var number = GetNumberOfCustomers();
if (number <= 1)
{
// if number of currents customers is now zero - end the downcounting thread
SetNumberOfWaitingCustomers(0);
updaterThread = null;
return;
}
SetNumberOfWaitingCustomers(number - 1);
}
}
}
}
Comment: You can consider using jQuery for some timer down-counting script. Showing something like: You can be served in 40 minutes ;-)
Yes Ajax is the key. It can be used by your website to communicate with your server unnoticeably.
An alternative approach would be to not update the count in the database but simply use a query to determine the number of customers within a certain time period. You can do this by modifying the model so that instead of QueueNumber it uses an arrival time and changing the controller so that it inserts a new Data record.
{
public class Data
{
public int ID { get; set; }
public DateTime Arrival_Time { get; set; }
}
public class DataDBContext : DbContext
{
public DbSet<Data> Queue { get; set; }
}
}
This way, as others have suggested you can use AJAX to poll for the number of people in the queue with a controller action that might look something like this:
[HttpGet]
public ActionResult NumberOfPeopleInQueue()
{
var result = db.NumberOfCustomersSince(DateTime.Now.AddMinutes(-20));
return Json(result);
}
The nice thing about this approach is that should haircuts start to take longer (say 30 minutes) you can simply change the query and the application continues to work.