C#:如何以适当有效的方式为许多对象实现保持活动的计时器?
我想实现的很容易解释,但因为有这么多不同的可能性,我并不真正了解每种可能方法的利弊: 在我的应用程序中有很多(比如说数千个通信对象)。 当这样的对象空闲一段时间(意味着某些方法没有被调用)时,它应该被简单地关闭(这里详细的意思与此无关) 我想到了一个与每个对象相关联的“计时器”,每当我“使用”对象时,它都会被重新触发。比如:C#:如何以适当有效的方式为许多对象实现保持活动的计时器?,c#,async-await,timer,task,C#,Async Await,Timer,Task,我想实现的很容易解释,但因为有这么多不同的可能性,我并不真正了解每种可能方法的利弊: 在我的应用程序中有很多(比如说数千个通信对象)。 当这样的对象空闲一段时间(意味着某些方法没有被调用)时,它应该被简单地关闭(这里详细的意思与此无关) 我想到了一个与每个对象相关联的“计时器”,每当我“使用”对象时,它都会被重新触发。比如: public void ReTrigger() { lock (_some_locking) { //Reset the timer
public void ReTrigger()
{
lock (_some_locking)
{
//Reset the timer
_timer.Stop();
_timer.Start();
}
}
}
注意,我的应用程序大量使用async/await,我希望使用最适合这个概念的解决方案。我想避免为了运行大量计时器而增加大量线程
有许多不同的计时器可用:
System.Timers.Timer
System.Threading.Timer
System.Windows.Forms.Timer
System.Web.UI.Timer
System.Windows.Threading.DispatcherTimer
那么,哪一个“适合”我使用asyncio的概念呢
换言之,依赖像这样的背景任务会更好吗
while (true)
{
try
{
await Task.Delay(timeout, _cancellationToken)
... some action when expired ...
}
catch (TaskCanceledException)
{
// we have been re-triggered by "using" the object
}
}
这更符合我的概念,但是,在这种情况下,每次重新触发后我都需要一个新的取消令牌,这不是很好
解决我的问题的“黄金之路”是什么;最好使用异步任务
另一个解决方案是内务管理任务,它周期性地轮询所有活动对象是否过期。这只需要一个正在运行的计时器,但也不是很好。下面是一种被动跟踪对象过期状态的方法,无需使用计时器。每个对象上次使用的时间存储在私有
double
字段中,该字段在每次使用该对象时都会更新。如果对象长时间未使用,该字段将采用值double.MaxValue
,表示“过期”,并将永久保留该值。下面的GetExpired
方法处理以原子方式和线程安全性比较和更新字段的复杂性:
public static bool GetExpired(ref double lastUsed, TimeSpan slidingExpiration,
bool touch)
{
// Magic values, 0: Not initialized, double.MaxValue: Expired
double previous = Volatile.Read(ref lastUsed);
if (previous == double.MaxValue) return true;
// Get current timestamp in seconds
double now = (double)Stopwatch.GetTimestamp() / Stopwatch.Frequency;
if (previous == 0D || now - previous < slidingExpiration.TotalSeconds)
{
// Not expired (unless preempted)
if (!touch) return false;
var original = Interlocked.CompareExchange(ref lastUsed, now, previous);
return original == double.MaxValue;
// In any other case that original != previous we've lost the race to update
// the field, but its value should be very close to 'now'. So not expired.
}
else
{
// Expired (unless preempted)
var original = Interlocked.CompareExchange(ref lastUsed, double.MaxValue,
previous);
return original == double.MaxValue || original == previous;
}
}
你不能用过期策略将它们存储在内存缓存中,并在使用时更新吗?当通信对象关闭时,你想在那一刻发生什么事情吗?如果是,您是否对调用此内容的线程有任何偏好?如果是,您是否对调用某物的计时器的准确性有任何偏好?例如,对的解决方案是什么?是不是太准确了?太不准确了?刚刚好?你查过连接池了吗?如果您使用的是标准框架类,那么它们会为您解决所有这些问题。计时器必须不准确。如果精确到几秒钟,就可以了。当某个物体已经5分钟没有使用时,它应该被放置在背景中。你预计在任何时候最多有多少个物体?另外,平均来说,您要多久调用一个对象上的方法才能重新启动其过期计时器?我理解这一点,但这不是我真正需要的:在您的建议中,我必须调用TryDoSomething来尝试对该对象执行某些操作,当该对象过期时,我会注意到这一点。但是,我想在一段时间没有使用对象的情况下完全删除该对象,因此该机制必须在后台自动运行。当然,轮询是一种选择,但我想避免它。@MichaelW-hmmm。我认为及时处理过期对象的要求是一个重要信息,应该包括在问题中。你能编辑这个问题并把它添加到那里吗?顺便说一句,请查看颇受欢迎的
MemoryCache
组件:“过期不会在后台发生。没有计时器主动扫描缓存中的过期项目。缓存上的任何活动(获取、设置、删除)都可以触发对过期项目的后台扫描。”
public class MyComObject
{
private readonly TimeSpan _slidingExpiration = TimeSpan.FromSeconds(60);
private double _lastUsed;
public MyComObject() // Constructor
{
GetExpired(ref _lastUsed, default, touch: true); // Start expiration "timer"
}
public bool IsExpired => GetExpired(ref _lastUsed, _slidingExpiration, touch: false);
public bool TryDoSomething()
{
if (GetExpired(ref _lastUsed, _slidingExpiration, touch: true)) return false;
//...
return true; // The job was done
}
}