我有一个Timer
正在进行60秒的倒计时。当滴答声达到60秒时,它会停止并处理 - 没问题(我认为)。这是在WebApi
服务的上下文中运行的。我需要能够从UI取消倒计时,所以我暴露了一种方法来处理这个问题。由于控制器是瞬态的(感谢Luaan),并且正如Daniel指出的那样,应用程序池是不可预测的,我需要一种方法来向客户端发送“可取消”的倒计时。任何想法?
[HttpGet]
public IHttpActionResult CancelCountdown()
{
// DOES NOTHING BECAUSE THERE'S A NEW INSTANCE OF THE CONTROLLER
timer.Stop();
timer.Dispose();
return Ok();
}
private void StartCountdown()
{
// MAY BE A BAD SOLUTION BECAUSE THE APP POOL MAY RECYCLE
timer.Interval = _timeIntervalInMilliseconds;
timer.Elapsed += BroadcastToClients;
timer.Start();
}
private void BroadcastToClients(object sender, EventArgs e)
{
_elapsed += 1;
if (_elapsed == _duration)//_duration is 60
{
timer.Stop();
timer.Dispose();
return;
}
_messageHub.Clients.All.shutdown(_elapsed);
}
答案 0 :(得分:1)
如果不知道你想要用这个来完成什么就很难提供一个适当的解决方案,但是我会试一试。
正如Luaan指出的那样,控制器基本上是无状态的,因此除了它的外部依赖性之外,你不应该将实例变量放在它们上,因为每个请求都会创建一个新的控制器类实例。
您可以将计时器存储在静态字典中,并由GUID编制索引,然后在控制器上返回GUID并将其用作取消令牌。
类似的东西:
private static Dictionary<string,Timer> timers = new Dictionary<Guid,Timer>();
public Guid StartCountdown()
{
// MAY BE A BAD SOLUTION BECAUSE THE APP POOL MAY RECYCLE
timer.Interval = _timeIntervalInMilliseconds;
timer.Elapsed += BroadcastToClients;
var guid = Guid.NewGuid().ToString();
timers.Add(guid,timer);
timer.Start();
return guid;
}
public IHttpActionResult CancelCountdown(Guid cancelationToken)
{
//If the timer no longer exist or the user supplied a wrong token
if(!timers.HasKey(cancelationToken)) return;
var timer = timers[cancelationToken];
timer.Stop();
timer.Dispose();
timers.Remove(cancelationToken);
}
然而,这无法解决AppPool回收的问题。对于更强大的解决方案,您可以将每个倒计时的开始日期和时间存储在更永久的存储(例如SQL数据库,NoSQL数据库,redis服务器或其他)中,而不是使用计时器,并且具有正在运行的线程或全局计时器,或类似Hangfire,在启动时初始化,不断检查您的倒计时存储。如果有足够的时间发送广播消息,则将其发送,并将倒计时标记为已完成。如果用户想要取消倒计时,控制器将只读取适当的记录,将其标记为已取消,并且正在运行的线程可以忽略它。
如果采用这种方法,您需要考虑一些注意事项:
如果定时器间隔设置得太短,则可能会出现性能瓶颈,因为必须经常访问永久存储器。如果间隔太长,倒计时将不会太精确。
要缓解此问题,您可以将倒计时开始时间存储在永久存储中,以防应用程序池重置并且您需要还原它们。并将它们存储在静态变量的内存中,以便更快地访问。
请注意,如果您使用的是服务器场而不是单个服务器,则不会跨实例共享静态变量。