线程和单身人士

时间:2009-06-10 16:30:17

标签: c# multithreading singleton timer

在有人向我讨论单身人士之前,我会说在这种情况下,对我来说,在我的代码中广泛使用这个对象并且如果某人有更好的方式缺少DI,那么我将有意义喜欢听,但我希望这不会成为这篇文章的重点,更有助于解决这个问题。

这就是说这里的问题: 似乎在一段时间后我失去了对我的班级调度程序的引用,并且里面有一个不再触发的计时器滴答。这是因为它是以单身方式使用,一旦失去参考,它就是GC'd?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace MessageQueueInterface
{
    public class Scheduler
    {
        private const int _interval = 1000;
        private readonly Dictionary<DateTime, Action> _scheduledTasks = new Dictionary<DateTime, Action>();
        private readonly Timer _mainTimer;

        public Scheduler()
        {
            _mainTimer = new Timer();
            _mainTimer.Interval = _interval;
            _mainTimer.Tick += MainTimer_Tick;
            _mainTimer.Start();
        }

        void MainTimer_Tick(object sender, EventArgs e)
        {
            CheckStatus();
        }

        public void Add(DateTime timeToFire, Action action)
        {
            lock (_scheduledTasks)
            {
                if (!_scheduledTasks.Keys.Contains(timeToFire))
                {
                    _scheduledTasks.Add(timeToFire, action);
                }
            }
        }

        public void CheckStatus()
        {
            Dictionary<DateTime, Action> scheduledTasksToRemove = new Dictionary<DateTime, Action>();
            lock (_scheduledTasks)
            {
                foreach (KeyValuePair<DateTime, Action> scheduledTask in _scheduledTasks)
                {
                    if (DateTime.Now >= scheduledTask.Key)
                    {
                        scheduledTask.Value.Invoke();
                        scheduledTasksToRemove.Add(scheduledTask.Key, scheduledTask.Value);
                    }
                }
            }
            foreach (KeyValuePair<DateTime, Action> pair in scheduledTasksToRemove)
            {
                _scheduledTasks.Remove(pair.Key);
            }
        }
    }
}

在其他类

中以下列方式访问它
ApplicationContext.Current.Scheduler.Add(DateTime.Now.AddSeconds(1), ResetBackColor);

ApplicationContext是我的单身人士

我也知道日期时间对象不是字典的最佳KEY,但它适合我的目的

这是单身人士

public class ApplicationContext
{
    private static ApplicationContext _context;
    private Scheduler _scheduler;

    public Scheduler Scheduler
    {
        get { return _scheduler; }
    }

    private void SetProperties()
    {
        _scheduler = new Scheduler();
    }

    public static ApplicationContext Current
    {
        get
        {
            if (_context == null)
            {
                _context = new ApplicationContext();
                _context.SetProperties();

            }
            return _context;
        }
    }
}

5 个答案:

答案 0 :(得分:3)

您发布的课程似乎不是您的单身课程。该代码会更有帮助。

无论如何,是的,如果计时器超出范围并且被GC控制,那么它将停止触发事件。更有可能的是,调度程序立即超出范围,并且在发生这种情况和GC发生之间只有一段延迟。

发布您的单身代码可以让我或其他任何人给出更具体的答案。

修改

鉴于单身人士课程的简单性,跳出来的唯一潜在问题是Current属性的竞争条件。鉴于你没有锁定任何东西,那么两个线程在它为null时同时访问Current属性可能最终会有不同的引用,而最后一个被设置的线程将是唯一一个有引用的线程其范围将超出财产获取者本身的范围。

我建议创建一个简单的sync object实例作为静态成员,然后将其锁定在getter中。这应该可以防止这种情况出现。

顺便说一句,SetProperties()方法的目的是什么,而不是在无参数构造函数中甚至在声明点初始化变量?拥有这样的函数可以创建一个新的Scheduler对象并放弃现有的对象。

答案 1 :(得分:2)

您是否说问题是您对Scheduler()的全局/静态指针变为空?如果是这种情况,我们需要看到操作该引用的代码在失败时理解。

我确实有一些反馈......你对_scheduledTasks.Remove()的调用应该在锁内发生,以防止竞争条件改变列表。您对scheduledTask.Value.Invoke()的调用也应该发生在锁定之外,以防止有人在实现Invoke时在该锁定中排队另一个工作项。我会通过在离开锁之前将到期任务移动到堆栈本地列表,然后从该列表执行任务来完成此操作。

然后考虑如果Invoke()调用抛出异常会发生什么,如果本地列表中有剩余的任务,它们可能会被泄露。可以通过捕获/吞咽异常来处理,或者每次锁定时只将1个任务从队列中拉出来。

答案 2 :(得分:1)

由于您尚未发布单身人士本身的代码,因此很难说是否存在任何问题。但是,不,只要对象可以从静态变量(或堆栈上的任何东西)访问,它就不应该得到GC。这意味着你的问题是...别的。

假设有多个线程访问该对象,我怀疑会出现一些同步问题。

答案 3 :(得分:0)

System.Windows.Forms.Timer不太可靠,因为它喜欢在奇数时间获得GC。

请尝试使用System.Threading.Timer。

答案 4 :(得分:0)

您的应用程序是否是常规的WinForms程序?

System.Windows.Forms.Timer侦听程序消息循环中的计时器消息。如果你的程序不是WinForms,或者如果你在UI线程上做了很多工作(这绝不是一个好主意),你应该使用System.Timers.Timer。请注意,它可以在多个线程上触发事件;见here