简体   繁体   English

线程和单例

[英]Threading and Singletons

Before anyone flips out on me about singletons, I will say that in this instance it makes sense for me to have a singleton given the wide use of this object throughout my code and if someone has a better way short of DI I would like to hear but I would hope that this would not be the focus of this post, moreso helping solve it would be. 在有人向我介绍单例之前,我要说的是,在这种情况下,考虑到我的代码在整个代码中的广泛使用,让我拥有一个单例是有意义的,如果有人能更好地利用DI,我想听听但我希望这不会成为这篇文章的重点,而且会帮助解决它。

That being said here's the issue: It seems after a given amount of time that I am losing a reference to my class scheduler and inside there is a timer tick that no longer fires. 就是说,这就是问题所在:似乎在给定的时间后,我丢失了对我的类调度程序的引用,并且内部有一个不再触发的计时器滴答声。 Is this because it is being used in a singleton fashion and once it loses a reference it is GC'd? 这是因为它以单例方式使用,并且一旦失去参考就被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);
            }
        }
    }
}

it is accessed in the following way in other classes 在其他类中,可以通过以下方式访问它

ApplicationContext.Current.Scheduler.Add(DateTime.Now.AddSeconds(1), ResetBackColor);

ApplicationContext is my singleton ApplicationContext是我的单身

also i am aware that a datetime object is not the best KEY for a dictionary, but it suits my purposes here 我也知道日期时间对象不是字典的最佳键,但是它适合我的目的

here's the singleton 这是单身人士

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

The class that you posted doesn't appear to be your singleton class. 您发布的课程似乎不是您的单例课程。 That code would be more helpful. 该代码将更有帮助。

In any event, yes, if the timer were to fall out of scope and be GC'ed, then it would stop firing the event. 无论如何,是的,如果计时器超出范围并进行GC处理,它将停止触发该事件。 What is more likely is that the scheduler is immediately falling out of scope and there is just a delay between when that happens and when GC occurs. 更有可能的是,调度程序立即超出范围,并且在何时发生与何时发生GC之间仅存在一个延迟。

Posting your singleton code would allow me or anyone else to give a more specific answer. 发布您的单例代码将使我或其他任何人都可以提供更具体的答案。

Edit 编辑

Given the simplicity of the singleton class, the only potential issue that jumps out at me is a race condition on the Current property. 鉴于单例类的简单性,唯一让我跳出来的潜在问题是Current属性上的竞争条件。 Given that you don't lock anything, then two threads accessing Current property at the same time when it's null could potentially end up with different references, and the last one that gets set would be the only one that has a reference whose scope would extend beyond the scope of the property getter itself. 假设您没有锁定任何内容,那么当两个线程同时访问Current属性为null时,它们可能同时以不同的引用结尾,而最后一个被设置的线程将是唯一具有范围扩大的引用的线程超出了属性获取器本身的范围。

I would recommend creating a simple sync object instance as a static member, then locking it in the getter. 我建议将一个简单的同步object实例创建为静态成员,然后将其锁定在getter中。 This should prevent that condition from cropping up. 这样可以防止这种情况出现。

As an aside, what's the purpose for the SetProperties() method rather than initializing the variable in a parameterless constructor or even at the point of declaration? SetProperties()说一句, SetProperties()方法的目的是什么,而不是在无参数构造函数中甚至在声明时初始化变量? Having a function like this allows for the possibility of creating a new Scheduler object and abandoning the existing one. 拥有这样的功能可以创建一个新的Scheduler对象并放弃现有的对象。

Are you saying the issue is that your global/static pointer to Scheduler() becomes null? 您是说问题在于,指向Scheduler()的全局/静态指针变为空吗? If thats the case we'd need to see code that manipulates that reference to understand while it fails. 如果是这样,我们需要查看操纵该引用的代码以在失败时理解。

I do have some feedback though... Your calls to _scheduledTasks.Remove() should happen within the lock, to prevent race conditions changing the list. 不过我确实有一些反馈...您对_scheduledTasks.Remove()的调用应在锁内进行,以防止竞争条件更改列表。 Also your calls to scheduledTask.Value.Invoke() should happen outside the lock, to prevent someone from queueing another workitem within that lock when they implement Invoke. 同样,您对ScheduledTask.Value.Invoke()的调用也应该在锁之外进行,以防止某人在实现Invoke时在该锁内排队另一个工作项。 I would do this by moving due tasks to a list local to the stack before leaving the lock, then executing tasks from that list. 为此,我将在离开锁之前将到期任务移到堆栈本地的列表中,然后从该列表中执行任务。

Then consider what happens if an Invoke() call throws an exception, if there are remaining tasks in the local list they may be leaked. 然后考虑如果Invoke()调用引发异常,如果本地列表中还有剩余任务,它们可能会泄漏,该怎么办。 The can be handled by catching/swallowing the exceptions, or only pulling 1 task out of the queue each time the lock is picked up. 可以通过捕获/吞下异常来处理,或者每次拿起锁时仅将1个任务从队列中拉出。

Since you haven't posted the code for the singleton itself, it's hard to say if there are any problems in that. 由于您尚未发布单例本身的代码,因此很难说是否有任何问题。 But no, as long as the object is reachable from a static variable (or anything on the stack), it should not get GC'ed. 但是不,只要对象可以通过静态变量(或堆栈中的任何内容)访问,就不应进行GC处理。 Which means your problem is... something else. 这意味着您的问题是...其他问题。

I'd suspect some synchronization issue assuming multiple threads access the object. 假设有多个线程访问该对象,我会怀疑一些同步问题。

System.Windows.Forms.Timer isn't very reliable in that it likes to get GC'd at odd times. System.Windows.Forms.Timer不太可靠,因为它喜欢在奇怪的时间获取GC。

Try using System.Threading.Timer instead. 尝试改用System.Threading.Timer。

Is your application a regular WinForms program? 您的应用程序是常规的WinForms程序吗?

System.Windows.Forms.Timer listens for timer messages on your program's message loop. System.Windows.Forms.Timer在程序的消息循环中侦听计时器消息。 If you're program isn't WinForms, or if you do a lot of work on the UI thread (which is never a good idea), you should probably use System.Timers.Timer . 如果您的程序不是WinForms,或者您在UI线程上做了很多工作(这从来都不是个好主意),则可能应该使用System.Timers.Timer Please note that it can fire events on multiple threads; 请注意,它可以在多个线程上触发事件; see here 这里

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

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