【问题标题】:Increment a Temporary / Timed Variable in C#在 C# 中增加一个临时/定时变量
【发布时间】:2012-09-08 00:01:27
【问题描述】:

请原谅我对这个问题的无知,因为我对 C# 完全陌生,但是如何设置和处理临时变量或定时变量?例如,每当事件触发时,我想增加一个计数器。如果计数器在 60 秒内超过 3,我想触发另一个事件。否则,如果 60 秒后计数器没有增加,它会重置回 0。

我应该使用 MemoryCache 吗?或者我可以为变量设置一些漂亮的标志以使它们在指定的持续时间后取消设置(假设它们没有刷新)?完成这个简单任务的最佳方法是什么?请注意,我不一定要找人为我写;只是一个有用的手或建议,可以为我指明正确的方向。这是我要完成的工作的大致轮廓:

private static int totalCount = 0;
private static double maxCount = 3;
private static double timeLimit = 60;

private static void TestEvent(object src, EventArgs mea)
{
  totalCount++;
  if (totalCount > maxCount)
  {
    DoSomething();
  }
}

感谢您提供的任何帮助。我强调总是奖励有用的答案。

【问题讨论】:

  • 你想要的最终结果是什么,只是为了帮助澄清和打开地板以“清理”答案,例如,如果你的目标是在尝试太多失败后锁定一个帐户,它有助于阅读知道什么您正在寻找。
  • 你的问题是什么?每 60 秒检查一次状态或引发事件?
  • 事件触发的间隔使得它在前 60 秒内仅触发两次(事件 1 和 2),但之后立即触发 3 次(事件 3、4 和 5)所以事件 2 到 5 会在 60 秒内发生吗?如果您在前 60 秒后清除计数,您将错过这一点。你真的不需要某种滑动窗口来捕捉你正在寻找的情况吗?
  • John Skyor:最终结果是如果在 60 秒内发生 3 次其​​他事情,则触发一个事件以阻止进程运行。
  • Cyber​​maxs:对不起,我不明白你的问题。

标签: c# variables timer intervals


【解决方案1】:

您可以维护一个Queue<T>(请参阅http://msdn.microsoft.com/en-us/library/7977ey2c.aspx),其中每个条目是事件触发的时间。当事件触发时,您首先从队列中删除任何超过 60 秒的条目(这很容易,因为队列是按时间排序的,并且队列的头部是最旧的​​条目),然后添加一个新条目对于刚刚触发的事件。如果队列的数量超过了您的阈值,那么您已经满足了您正在寻找的条件。

我建议使用DateTime.UtcNow 而不是DateTime.Now 存储在Queue<T> 中的时间。 UtcNow 速度更快,它避免了从夏令时转换到标准时间以及反之亦然可能导致的问题。

这是我脑海中的一些代码(可能需要一些修复):

private static Queue<DateTime> eventQueue = new Queue<DateTime>();
private static int timeWindowSeconds = 60;
private static int threshold = 3;

private static void TestEvent(object src, EventArgs mea) {
    DateTime now = DateTime.UtcNow;
    DateTime tooOld = now.AddSeconds(-timeWindowSeconds);

    // remove old entries
    while((eventQueue.Count > 0) && (eventQueue.Peek() < tooOld)) {
        eventQueue.Dequeue();
    }

    // add new entry
    eventQueue.Enqueue(now);

    // test for condition
    if (eventQueue.Count >= threshold) {
        eventQueue.Clear();
        DoSomething();           
    }
}

【讨论】:

  • 谢谢,我将首先尝试其他一些答案,因为它们可能足以满足我的需求,但我认为您的最终可能会成为解决方案。我只需要弄清楚如何构建队列系统。
  • @daveycroqet - .Net 有一个可以使用的 Queue 类。我已经更新了我的答案,包括一个指向它的文档的链接。
【解决方案2】:

你可以这样做:

private static int totalCount = 0;
private static double maxCount = 3;
private static TimeSpan timeLimit = TimeSpan.FromSeconds(60);
private static DateTime lastIncrementTime;

private static void TestEvent(object src, EventArgs mea)
{
    // If the time between now and lastIncrementTime is more than the timeLimit...
    if(DateTime.Now - lastIncrementTime > timeLimit)
    {
        totalCount = 0;
    }
    lastIncrementTime = DateTime.Now;

    totalCount++;     
    if (totalCount > maxCount)
    {
        DoSomething();
    }
}

【讨论】:

  • 处理功能,但也许添加一个计时器对象来增量调用该功能,而不是仅仅循环它?
  • 我将测试您的代码并进行试验,尽管我不太了解第一个 If 语句。无论如何,谢谢你,感谢你的帮助。
【解决方案3】:

您可以使用StopWatch 类。

在表单加载事件上(如果您想从该事件开始计算 60 秒),启动计时器, 每次在点击事件中,检查它被调用了多少秒左右。

【讨论】:

  • 注明。我现在正在研究秒表。谢谢!
【解决方案4】:

我会这样做:

private class SomeEventMonitor
{
    public  int      Threshold       { get ; private set ; }
    public  TimeSpan ThresholdWindow { get ; private set ; }

    private DateTime  marker ;
    private int       count  ;

    /// <summary>
    /// public constructor
    /// </summary>
    /// <param name="threshold"></param>
    /// <param name="window"></param>
    public SomeEventMonitor( int threshold , TimeSpan window )
    {
        if ( threshold <  1             ) throw new ArgumentOutOfRangeException("threshold") ;
        if ( window    <= TimeSpan.Zero ) throw new ArgumentOutofRangeException("window") ;

        this.Threshold       = threshold ;
        this.ThresholdWindow = window    ;

        Reset() ;

        return ;
    }

    private void Reset()
    {
        this.marker       = DateTime.Now ;
        this.count        = 0            ;
        return ;
    }

    public event EventHandler ThresholdExceeded ;

    private static readonly object latch = new object() ;
    public void EventWatcher( object source , EventArgs eventArgs )
    {
        lock ( latch )
        {
            DateTime current = DateTime.Now ;

            if ( ++count > Threshold )
            {
                TimeSpan window = current -marker ;
                if ( window > ThresholdWindow )
                {
                    ThresholdExceeded( this , new EventArgs() ) ;
                    Reset() ;
                }
            }

        }
        return ;
    }

}

【讨论】:

    【解决方案5】:

    您可以使用另一个变量来记录更改totalcount 值的时间。用当前时间检查它,然后做任何你想做的事情。
    这是代码...

    private static int totalCount = 0;
    private static double maxCount = 3;
    private static double timeLimit = 60;
    private static DateTime  timeflag= DateTime.Now;
    
    private static void TestEvent(object src, EventArgs mea)
    {
        if (timeflag.AddSeconds(timeLimit) < DateTime.Now)
        {
            totalCount = 0;
        }
        totalCount++;
        timeflag = DateTime.Now;
        if (totalCount > maxCount)
        {
                DoSomething();
        }
    }
    

    【讨论】:

    • 您的代码有错误。您需要&lt;,而不是&gt; 在您的timeFlag 检查中,否则您将重置totalCount,只要在最后timeLimit 秒内调用TestEvent,而不是在它在最近的timeLimit 秒内没有被调用。
    • 谢谢。我要玩这个,看看我是否可以让它按照我想要的方式工作。感谢您的帮助。
    • @JonSenchyna:哎呀!!我犯了一个错误。我的错。谢谢。
    【解决方案6】:

    有无数种方法可以解决这个问题。我首先想到的是使用在后台线程上启动的 Stopwatch 对象或 Timer 对象,然后编写一个可以订阅您感兴趣的事件的事件处理程序。当事件发生时,您的处理程序会触发,允许您暂停计时器并查询经过的时间,并相应地做出递增/重置决定。

    这只是一个概念的非常粗略的草图,但应该会给您一些前进的想法。祝你好运。

    根据上面@hatchet 的评论,这听起来几乎开始像一个队列,其中包含“即将过期”的成员或“滑动窗口”事件视界,如果该评论准确反映了您的问题,您必须捕获。

    编辑作为我的边缘强迫症,我对您最初的问题进行了一些思考,并提出了一个可能与您的问题相关或可能不相关的概念,但至少对于学术练习我将发布我所做的。在您的原始帖子中引起我注意的是 过期或定时变量 的概念,我认为这很新颖。您的问题指定您想要在给定的时间间隔过去时做一些特定的事情。

    我试图将这个想法抽象成一个通用的形式,想着这样一个想法可能有用的几种方式。想到的一个想法是在游戏环境中,(例如)在“自毁”之前,玩家可能只能看到一条消息 20 秒。我可以想象将过期“管道”连接到类型中可能会非常方便。另一种情况可能是在 CAI 环境中,其中 System.Drawing.Image 应该只显示固定时间,然后消失 - 同样,内置到期和计时代码的情况可能很有用。

    因此,至少考虑到这么多概念上的实用性,我开始工作,我拼凑的东西(我不会假装它全面或完整)是 Expiring 类型的泛型,表示为 @987654321 @。我整理的基线代码如下:

        // First stab at an "expiring" type that is only valid for a set interval.
    public class Expiring<T>
    {
        public delegate void ExpiredHandler(object sender, EventArgs e);
        public event ExpiredHandler OnExpired;
    
        T instance;
        int signaledCount = 0;
        long milliseconds = 0;
        bool isExpired = false;
        bool exceptOnExpiredReference = true;
        System.Timers.Timer lapseTimer = new System.Timers.Timer();
    
        public Expiring(T value)
        {
            instance = value;
        }
    
        public virtual void TimerElapsed(object sender, System.Timers.ElapsedEventArgs args)
        {
            if (OnExpired != null)
            {
                OnExpired(this, null);
            }
    
            isExpired = true;
        }
    
        public Expiring(T startValue, long expirationInterval, bool throwElapsedReferenceException):this(startValue)
        {
            milliseconds = expirationInterval;
            lapseTimer.AutoReset = true;
            lapseTimer.Interval = milliseconds;
            exceptOnExpiredReference = throwElapsedReferenceException;
            lapseTimer.Elapsed+=new System.Timers.ElapsedEventHandler(TimerElapsed);
            this.Set();
    
        }
    
    
        public void Set()
        {
            signaledCount++;
            lapseTimer.Stop();
            lapseTimer.Start();
        }
    
        public T Value
        {
            get
            {
                if (!isExpired || !exceptOnExpiredReference)
                    return instance;
                else
                    throw new InvalidOperationException("Reference to an expired value.");
            }
            set
            {
                instance = value;
            }
        }
    }
    

    这里的想法是有人可以声明一个Expiring&lt;int&gt;,指定它的初始值、过期时间和一个值,以指示在过期间隔过后访问实例的值是否应该抛出异常。当 expireInterval 过去时,引发 OnExpired 事件,允许声明者指定自定义事件处理程序以在值到期时提供自定义操作。

    如果调用者希望重置过期计时器,他只需要调用对象的 Set() 方法。这也增加了我最终没有使用的内部“signaledCount”值,但在确定到期计时器被重置的次数方面正在考虑。如果在过期间隔过后访问对象的 Value 属性,则会引发 InvalidOperationException 并显示“Value has expired”消息。

    很遗憾,这个想法的理论/学术价值多于实际价值。如果可以将所有算术运算符重载到本机值类型的实现中,它将具有更多的实用性,但我很快发现 C# 根本不喜欢这个概念(并在此处发现关于这个主题的相当广泛的帖子在这里)。理想情况下,我很想能够这样说:

    Expired<Int32> foo = new Expired<Int32>(5,10000,true);
    Expired<Int32> bar = new Expired<Int32>(10,10000,true);
    Expired<Int32> baz = foo+bar;  // can't make that work
    

    有人认为这个问题可以用动态类型来解决,但我现在选择不去追求它。正如我所敲定的那样,这个想法供讨论,因为它适用于 OP 的“定时变量”概念的一般视图。鼓励和欢迎建设性的 cmets/批评/改进。

    【讨论】:

    • 你们中的两个人现在建议了秒表,我打算尽快调查一下。我可能最终会去排队,但在我弄清楚该怎么做之前我不能确定。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-29
    • 2013-03-15
    • 2014-02-01
    • 2011-05-21
    • 2012-03-19
    • 1970-01-01
    相关资源
    最近更新 更多