0

原谅我对这个问题的无知,因为我对 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();
  }
}

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

4

6 回答 6

4

您可以维护一个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();           
    }
}
于 2012-09-14T20:07:26.600 回答
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();
    }
}
于 2012-09-14T19:55:32.697 回答
1

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

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

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

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

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

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

    // 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<int>,指定它的初始值、过期时间和一个值,以指示在过期间隔过后尝试访问实例的值是否应该抛出异常。当 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 的“定时变量”概念的一般视图。鼓励和欢迎建设性的评论/批评/改进。

于 2012-09-14T19:55:24.347 回答
1

您可以使用StopWatch类。

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

于 2012-09-14T20:07:37.627 回答
1

您可以使用另一个变量来记录更改 的值的时间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();
    }
}
于 2012-09-14T19:53:21.353 回答
1

我会做这样的事情:

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 ;
    }

}
于 2012-09-14T21:01:36.210 回答