views:

123

answers:

2

I want is to remove a Notification from a ObservableCollection<Notification> after some timeout. Is there a better way than starting a new ThreadPool thread for each added item and Thread.Sleep in there?


Final code based on Nidonocu's answer:

public class NotificationCollection : ObservableCollection<Notification>
{
    private readonly DispatcherTimer timer;

    public NotificationCollection()
        : this(Application.Current.Dispatcher)
    {
    }

    public NotificationCollection(Dispatcher dispatcher)
    {
        this.timer =
            new DispatcherTimer(DispatcherPriority.DataBind, dispatcher);
        this.timer.Tick += this.TimerOnTick;
    }

    protected override void InsertItem(int index, Notification item)
    {
        base.InsertItem(index, item);
        if (!this.timer.IsEnabled)
        {
            this.StartTimer(item);
        }
    }

    private void StartTimer(Notification item)
    {
        var timeout = item.Timestamp + item.Timeout - DateTime.UtcNow;
        if (timeout < TimeSpan.Zero)
        {
            timeout = TimeSpan.Zero;
        }

        this.timer.Interval = timeout;
        this.timer.Start();
    }

    private void TimerOnTick(object sender, EventArgs e)
    {
        this.timer.Stop();

        this.RemoveAt(0);
        if (this.Count > 0)
        {
            this.StartTimer(this[0]);
        }
    }
+2  A: 

Wouldn't some kind of Timer be more appropriate? You could then just have one thread which if there were more items remaining it would resume the timer and check again a second later if its time for the next notification to be removed.


Edit: Since you are in .net 3.5 I am assuming WPF which uses a DispatcherTimer. This will automatically use the correct thread to run the method you pass it as I understand it. Here is UNTESTED code to try:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Windows;

namespace WpfApplication1
{
    public class Notification
    {
        public DateTime TimeStamp { get; set; }
    }

    public class NotificationCollection : ObservableCollection<Notification>
    {
        private readonly TimeSpan timeout;

        private DispatcherTimer timer;

        public NotificationCollection(TimeSpan timeout)
            : this(timeout, Application.Current.Dispatcher) { }

        public NotificationCollection(TimeSpan timeout, Dispatcher dispatch)
        {
            this.timeout = timeout;
            timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, this.Cleanup, dispatch);
        }

        protected override void InsertItem(int index, Notification item)
        {
            base.InsertItem(index, item);
            timer.Start();
        }

        private void Cleanup(object o, EventArgs e)
        {
            timer.Stop();
            // Sanity
            if (this.Count == 0)
                return;

            var deadList = from note in this.Items
                           where note.TimeStamp + this.timeout - DateTime.UtcNow < TimeSpan.Zero
                           select note;
            foreach (var note in deadList)
            {
                this.Remove(note);
            }

            if (this.Count > 0)
                timer.Start();
        }
    }
}
Nidonocu
Could you provide an example? All my attempts using only one thread failed, mainly caused by that the items have to be removed in the main thread.
dtb
Working on it...
Nidonocu
Thanks. I've modified it a bit and posted the result in the question area.
dtb
A: 

I would not create a thread for every inserted object. Instead I would have a single cleanup thread or use a timer object. When the thread wakes up it can walk through the list cleaning up old items.

I would also overide the index operator and any other accessor methods to not allow items that should be cleaned up but have not yet been.

Daniel