繁体   English   中英

线程和单例

[英]Threading and Singletons

在有人向我介绍单例之前,我要说的是,在这种情况下,考虑到我的代码在整个代码中的广泛使用,让我拥有一个单例是有意义的,如果有人能更好地利用DI,我想听听但我希望这不会成为这篇文章的重点,而且会帮助解决它。

就是说,这就是问题所在:似乎在给定的时间后,我丢失了对我的类调度程序的引用,并且内部有一个不再触发的计时器滴答声。 这是因为它以单例方式使用,并且一旦失去参考就被GC了吗?

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是我的单身

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

这是单身人士

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

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

无论如何,是的,如果计时器超出范围并进行GC处理,它将停止触发该事件。 更有可能的是,调度程序立即超出范围,并且在何时发生与何时发生GC之间仅存在一个延迟。

发布您的单例代码将使我或其他任何人都可以提供更具体的答案。

编辑

鉴于单例类的简单性,唯一让我跳出来的潜在问题是Current属性上的竞争条件。 假设您没有锁定任何内容,那么当两个线程同时访问Current属性为null时,它们可能同时以不同的引用结尾,而最后一个被设置的线程将是唯一具有范围扩大的引用的线程超出了属性获取器本身的范围。

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

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

您是说问题在于,指向Scheduler()的全局/静态指针变为空吗? 如果是这样,我们需要查看操纵该引用的代码以在失败时理解。

不过我确实有一些反馈...您对_scheduledTasks.Remove()的调用应在锁内进行,以防止竞争条件更改列表。 同样,您对ScheduledTask.Value.Invoke()的调用也应该在锁之外进行,以防止某人在实现Invoke时在该锁内排队另一个工作项。 为此,我将在离开锁之前将到期任务移到堆栈本地的列表中,然后从该列表中执行任务。

然后考虑如果Invoke()调用引发异常,如果本地列表中还有剩余任务,它们可能会泄漏,该怎么办。 可以通过捕获/吞下异常来处理,或者每次拿起锁时仅将1个任务从队列中拉出。

由于您尚未发布单例本身的代码,因此很难说是否有任何问题。 但是不,只要对象可以通过静态变量(或堆栈中的任何内容)访问,就不应进行GC处理。 这意味着您的问题是...其他问题。

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

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

尝试改用System.Threading.Timer。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM