4

场景:我正在构建一个调度系统,每个计时器事件我想运行一个自定义方法而不是通常的Timer.Elapsed事件。

所以我写了这样的东西。

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new Timer(timeToRun.TotalMilliseconds);
    schedule.Timer.Elapsed += delegate { Refresh_Timer(schedule); };
    schedule.Timer.AutoReset = true;
    schedule.Timer.Enabled = true;
}

好吧,很简单,实际上确实创建了我的计时器。但是,我希望每个 elapsed 事件都使用它传入的 schedule 元素运行。我的问题是,为什么 Elapsed 事件仅在 for 循环中的最后一个 ScheduleElement 中为每个 Timer.Elapsed 事件传递。

现在我知道什么可以解决它,我只是不知道为什么。如果我回滚到原始 Timer.Elapsed 事件并用我自己的类扩展 Timer 类,我可以解决它。像这样。

修复:

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new TimerEx(timeToRun.TotalMilliseconds);
    schedule.Timer.Elapsed +=new System.Timers.ElapsedEventHandler(Refresh_Timer);
    schedule.Timer.Tag = schedule;
    schedule.Timer.AutoReset = true;
    schedule.Timer.Enabled = true;
}

然后我将背面投射object sender到它的原始对象中,并Tag从它身上窃取财产,这为我提供了每个独特计时器的正确时间表。

再说一遍,为什么在 foreach 循环delegate { }的最后一个循环中为所有计时器使用唯一的传递?ScheduleElement

编辑 1

定时器类

public TimerEx : Timer {

    public TimerEx(double interval) : base(interval) { }

    private Object _Tag;

    public Object Tag {
        get { return _Tag; }
        set { _Tag = value; }
    }
}
4

1 回答 1

9

这是因为您在委托中使用了闭包,并且它关闭了同一个变量,该变量为 foreach 循环的每次迭代共享。

有关详细信息,请参阅 Eric Lippert 的文章关闭被认为有害的循环变量

在这种情况下,您可以轻松地使用临时修复它:

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new Timer(timeToRun.TotalMilliseconds);

    // Make a temporary variable in the proper scope, and close over it instead
    var temp = schedule;
    schedule.Timer.Elapsed += delegate { Refresh_Timer(temp); };

请注意,C# 5 更改了foreach循环的这种行为。如果你用最新的编译器编译它,问题就不再存在了。

于 2013-07-16T23:44:34.593 回答