C# 到达日期时间后执行方法的最有效方法

C# 到达日期时间后执行方法的最有效方法,c#,C#,我有一门课是这样的: public sealed class Contract { public bool isExpired { get; set; } public DateTime ExpirationDate { get; set; } public void MarkAsExpired() { this.isExpired = true; // Other stuff.. } } 我想做的是:一旦到达Expira

我有一门课是这样的:

public sealed class Contract
{
    public bool isExpired { get; set; }
    public DateTime ExpirationDate { get; set; }
    public void MarkAsExpired()
    {
        this.isExpired = true;
        // Other stuff..
    }
}
我想做的是:一旦到达
ExpirationDate
,就应该调用
MarkAsExpired
。如果程序关闭并且在重新打开时,
ExpirationDate
已过,则应发生同样的情况。如果
isExpired
为真,则不应发生任何事情

合同是可修改的,并且经常添加新的合同。我的确切预期音量未知

我已经想到了一种方法,可以通过
DateTime
对象的可推广扩展方法来实现:

var contracts = new List<Contract>();
foreach (var contract in contracts.Where(contract => !contract.isExpired))
{
    contract.ExpirationDate.ExecuteWhenPassed(() => contract.MarkAsExpired());
}
var合同=新列表();
foreach(contracts.Where(contract=>!contract.isExpired)中的var合同)
{
contract.ExpirationDate.ExecuteWhenPassed(()=>contract.MarkAsExpired());
}
问题在于编写扩展方法本身。我已经创建了一个有效的解决方案:

static void ExecuteWhenPassed(this DateTime date, Action action)
{
    // Check if date has already passed
    if (date <= DateTime.Now)
    {
        action();
    }
    else
    {
        // Timer will fire when $date is reached
        var timer = new Timer { Interval = date.Subtract(DateTime.Now).TotalMilliseconds, AutoReset = false };
        timer.Elapsed += (s, e) => action();
        timer.Start();
    }
}
static void ExecuteWhenPassed(此日期时间日期,操作)
{
//检查日期是否已过
如果(日期动作();
timer.Start();
}
}
但是,我担心它的效率。具体地说,我担心为每个
合同
实例创建单独计时器所涉及的开销,其中可能有数百个实例尚未过期

开销大吗?如果是这样,处理这个问题的更有效方法是什么


只要我的过期处理问题得到解决,我愿意采用完全不同的方法。

我建议使用微软的反应式框架(NuGet“System.Responsive”)来实现这一点。它变得非常简单

代码如下:

List<Contract> contracts = new List<Contract>();

/* populate `contracts` here before `query` */  

IObservable<Contract> query =
    from i in Observable.Interval(TimeSpan.FromMinutes(1.0))
    from c in contracts
    where !c.isExpired
    where c.ExpirationDate <= DateTime.Now
    select c;

IDisposable subscription =
    query
        .Subscribe(c => c.MarkAsExpired());
List contracts=newlist();
/*在“查询”*/
可观测查询=
从i到可观测间隔(时间跨度从分钟(1.0))
合同中的c
哪里!c.我跑了
其中c.ExpirationDate c.MarkAsExpired());
这是在设置一个可观察计时器(
observable.Interval(TimeSpan.frommins(1.0))
),它每分钟触发一个值。然后,每分钟,它都会过滤合同列表,只过滤那些尚未到期且到期数据比现在更早的合同

然后在
订阅中
只获取这些值的流,并将它们标记为过期

如果要停止处理,只需调用
subscription.Dispose()
。这是一段非常简单的代码


如果在Windows窗体上运行此代码,则可以执行
.ObserveOn(InstanceOfforControl)
以返回到UI线程。在WPF上是
.ObserveOnDispatcher()
。该代码正好在
.Subscribe(…)
之前。没有必要在每个合同中使用一个计时器,您可以使用一个计时器来完成。查找下一个要过期的项目并创建计时器。计时器触发时-使此项过期,然后释放计时器并重复此过程。好处包括:项目大约在其
ExpirationTime
中规定的时间过期(无“每分钟\秒”轮询),最多一个计时器,如果没有要过期的项目-无计时器。示例代码(而不是使用单独的
MarkAsExpired
方法-您可以在
IsExpired
属性设置器中执行逻辑):

公共接口可扩展{
bool IsExpired{get;set;}
DateTime ExpirationTime{get;}
}
公共类过期管理器{
私有只读列表_items=new List();
公共到期管理器(IEnumerable items){
_items.AddRange(items);
触发器();
}
公共作废添加(IExpirable项){
锁定(_项)
_项目。添加(项目);
//重置当前计时器并重复
触发器();
}
删除公共无效项(IExpirable项){
锁定(_项)
_项目。移除(项目);
//重置当前计时器并重复
触发器();
}
私人定时器;
私有无效触发器(){
//先复位
如果(_timer!=null){
_timer.Dispose();
_定时器=空;
}
下一步可扩展;
锁定(_项){
next=\u items.Where(c=>!c.IsExpired).OrderBy(c=>c.ExpirationTime.FirstOrDefault();
}
if(next==null)
return;//没有其他要过期的项目
var dueTime=next.ExpirationTime-DateTime.Now;
如果(dueTime
以下答案假设一组活动合同,按时间排序

static List<Contract> ActiveContracts = new List<Contract>();
// ...
ActiveContracts.AddRange(contracts.Where(x => !x.isExpired));
ActiveContracts.Sort((e1, e2) => e1.ExpirationDate.CompareTo(e2.ExpirationDate));
在已过事件中,更新所有具有过去过期日期的合同,并根据将来最接近的过期日期设置新的时间间隔

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    var t = sender as System.Timers.Timer;
    while (ActiveContracts.Any())
    {
        var item = ActiveContracts[0];
        if (item.ExpirationDate > DateTime.Now)
        {
            break;
        }
        item.MarkAsExpired();
        ActiveContracts.RemoveAt(0);
    }
    if (ActiveContracts.Any())
    {
        // specially when debugging, the time difference can lead to a negative new interval - don't assign directly
        var nextInterval = (ActiveContracts[0].ExpirationDate - DateTime.Now).TotalMilliseconds;
        t.Interval = nextInterval > 0 ? nextInterval : 1;
        t.Start();
    }
    else
    {
        t.Stop();
    }
}

如果您希望合同列表在不同的时间间隔之间更改,则可能需要定义一个时间间隔上限(
If(nextInterval>XXX)nextInterval=XXX;
)。如果您希望用新合同重新填充空列表,请替换
t.Stop()通过
t.Interval=XXX

首先让我说,不管效率概念如何,您的解决方案都是优雅和完美的,但您是对的,拥有更多线程并不一定意味着实现一个高效的系统。现在,当您使用计时器时,实际上是在创建一个后台线程,它必须在正确的时间内勾选时间并启动所需的操作。现在根据证据,在真实情况下,你会有上百个。问题是,这些资源将在所有这些线程之间共享,如果您的cod是服务器端,这可能是一个严重的问题
// trigger initially in 1ms
System.Timers.Timer timer = new System.Timers.Timer(1);
timer.Elapsed += timer_Elapsed;
timer.AutoReset = false;
timer.Start();
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    var t = sender as System.Timers.Timer;
    while (ActiveContracts.Any())
    {
        var item = ActiveContracts[0];
        if (item.ExpirationDate > DateTime.Now)
        {
            break;
        }
        item.MarkAsExpired();
        ActiveContracts.RemoveAt(0);
    }
    if (ActiveContracts.Any())
    {
        // specially when debugging, the time difference can lead to a negative new interval - don't assign directly
        var nextInterval = (ActiveContracts[0].ExpirationDate - DateTime.Now).TotalMilliseconds;
        t.Interval = nextInterval > 0 ? nextInterval : 1;
        t.Start();
    }
    else
    {
        t.Stop();
    }
}
var timer = new Timer {Interval = ComputeAvgTime(), AutoReset = true};
timer.Elapsed += timer_Elapsed;
timer.Start();
 static double ComputeAvgTime()
        {
            //For all items wich are not expired yet.
            var contractExpTime = new List<DateTime>();
            var timeStamp = DateTime.Now;
            //Sample Data
            contractExpTime.Add(timeStamp.AddMilliseconds(2000));
            contractExpTime.Add(timeStamp.AddMilliseconds(3000));
            contractExpTime.Add(timeStamp.AddMilliseconds(5000));
            //
            var total = contractExpTime.Where(item => item > timeStamp).Aggregate<DateTime, double>(0, (current, item) => item.Subtract(timeStamp).TotalMilliseconds + current);
            var avg = total / contractExpTime.Count(item => item > timeStamp);
            return avg;
        }
static void timer_Elapsed(object sender, ElapsedEventArgs e)
 {
            //Fetch all items which are not expired yet.
            // Set all Expired.
            // Save all items.
 }