【问题标题】:Trigger an Event after x seconds, but also cancel it before it executes在 x 秒后触发事件,但在执行之前也将其取消
【发布时间】:2016-06-28 11:11:26
【问题描述】:

我正在开发一个 Web API(效果很好)。少了什么东西? 这是Get Action 的示例代码:

public IEnumerable<xxxx> Get()
{           
    IEnumerable<xxxx> yyyy = new List<xxxx>();
    //get yyyy from database
    timer = new Timer();
    timer.AutoReset = true;
    timer.Enabled = true;
    timer.Interval = 5000; //miliseconds
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    return yyyy;
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    //code to be executed when timer elapses...
}

因此,一旦收到请求,计时器将被初始化并以 5 秒的间隔触发 Elapsed 事件。在下一个后续请求中,这将继续......

预期的行为是这样的:

  1. 初始化请求 -1
  2. 初始化定时器 -1
  3. 如果在 5 秒内收到来自同一客户端的另一个请求,则计时器不得触发 elapsed 事件。
  4. 如果在 5 秒内未收到来自同一客户端的请求,则计时器应超时并触发事件。

而且计时器与客户端无关。

这是与此相关的进一步业务场景...... 我正在开发一个 Web API,它在打开时将被电子设备使用。只要电源可用,设备就会一直发送其 ON 状态。一旦用户关闭开关,对服务器的请求就会停止。

无论设备是开还是关,这些状态都会更新到数据库中。现在更棘手的部分是识别设备何时关闭(很复杂,因为如果设备停止发送任何请求,服务器不知道任何事情)。所以每个设备都有一个单独的计时器。

【问题讨论】:

  • 如何识别客户?
  • 您想在Elapsed 中运行什么代码?我认为这是一个 XY 问题。
  • 我真的不需要识别客户端......我的实现与客户端无关。该实现假定,所有客户端都是相似的,并且将使用相同的数据提供服务......但是,可以根据其他标准拥有多个计时器
  • @PatrickHofman Elapsed 中应该包含什么代码?它可以是任何东西......假设我正在初始化一个变量并且没有在其他任何地方使用。
  • 你必须向我们展示究竟会发生什么,因为这会影响方法。

标签: c# asp.net asp.net-mvc asp.net-web-api asp.net-web-api2


【解决方案1】:

首先,感谢@Patrick Hofman 指导我并跳出框框思考...... 我实现了一个内部有静态属性的类。

public class DeviceContainer
{
    public static List<DevTimer> timers=new List<DevTimer>();
}
public class DevTimer:Timer
{
    public string Identifier {get; set;}
    public bool IsInUse{get; set;}
}

然后在上面的代码(有问题)中,我做了以下更改:

public IEnumerable<xxxx> Get(string Id)
{        
    //Check if timer exists in
    if(!DeviceContainer.timers.Any(s=>s.Identifier.Equals(Id)))
    {
         //Create new object of timer, assign identifier =Id, 
         //set interval and initialize it. add it to collection as
         var timer = new DevTimer();
         timer.AutoReset = true;
         timer.Enabled = true;
         timer.Interval = 5000; //miliseconds
         timer.Elapsed += timer_Elapsed;
         timer.IsInUse=true;
         timer.Identifier=Id;             
         DeviceContainer.timers.Add(timer);
         timer.Start();
    }
    else
    {
         //Code to stop the existing timer and start it again.
         var _timer=DeviceContainer.timers.FirstOrDefault(s=>s.Identifier.Equals(Id)) 
                      as DevTimer;
         _timer.Stop();
         _timer.Start();
    }        

}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    //code that will turn off the device in DB
}

我没有发布整个代码,因为这不是这里的目的。

【讨论】:

    【解决方案2】:

    我会为此使用 Microsoft 的响应式框架。

    代码如下:

    IEnumerable<xxxx> yyyy = new List<xxxx>();
    
    Subject<Unit> clientRequestArrived = new Subject<Unit>();
    
    IDisposable subscription =
        clientRequestArrived
            .Select(_ => Observable.Interval(TimeSpan.FromSeconds(5.0)))
            .Switch()
            .Subscribe(_ =>
            {
                //code to be executed when timer elapses...
                //directly access `yyyy` here
            });
    

    您需要做的就是每次收到用户请求时调用clientRequestArrived.OnNext(Unit.Default);,这足以让此代码重置计时器。

    如果您想完全停止计时器,只需致电subscription.Dispose()

    【讨论】:

    • 这里有 2 个问题,这段代码会在阻塞我的 UI 或当前 HttpRequest 的主线程上运行吗?其次,上面的代码专门在 ASP.NET 的上下文中,它是无状态的,它们不是诸如全局变量或对象之类的东西。 'subscription' 和 'yyyy' 实例在后续请求中将无法访问。那是我的信念……我也可能是错的。但我肯定会研究 Reactive Framework
    • @HirenDesai - 我认为您正在运行某种长时间运行的服务以使您的计时器正常运行。并且此代码将自动推送到单独的线程。如果需要,Rx 中有一些方法可以轻松地编组回 UI。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-09
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    相关资源
    最近更新 更多