1
\$\begingroup\$

I use this in my Blazor application, letting other services send errors or warnings to this AlertService. The service triggers an update of a component which simply displays the messages, errors in red warnings in yellow.
While getting no new warnings, the existing warnings should be deleted, every 5 seconds one less warning being displayed. Same goes for errors.
When a new warning comes in, the timer restarts (so when the service was 1 second before deleting a warning, that timer is now void and the new timer starts with 5 seconds again).

This is my class, I feel there must be a better way but I haven't figured it out yet:

    public class AlertService
{
    private readonly ObservableCollection<string> errors = new ObservableCollection<string>();
    private readonly ObservableCollection<string> warnings = new ObservableCollection<string>();
    private object? talkingStickErrors;
    private object? talkingStickWarnings;

    public ReadOnlyObservableCollection<string> Errors { get; }

    public ReadOnlyObservableCollection<string> Warnings { get; }

    public AlertService()
    {
        Errors = new ReadOnlyObservableCollection<string>(errors);
        Warnings = new ReadOnlyObservableCollection<string>(warnings);
    }

    public void Error(string message)
    {
        Console.WriteLine($"Error: {message}");
        errors.Add(message);
        StartErrorTimer();
    }

    private void StartErrorTimer()
    {
        talkingStickErrors = new object();
        RemoveErrorAfterDelay(talkingStickErrors);
    }

    private async void RemoveErrorAfterDelay(object localTalkingStick)
    {
        while (true)
        {
            await Task.Delay(5000);
            if (localTalkingStick != talkingStickErrors || !errors.Any()) return;
            errors.RemoveAt(0);
        }
    }

    public void Warning(string message)
    {
        Console.WriteLine($"Warning: {message}");
        warnings.Add(message);
        StartWarningTimer();
    }

    private void StartWarningTimer()
    {
        talkingStickWarnings = new object();
        RemoveWarningAfterDelay(talkingStickWarnings);
    }

    private async void RemoveWarningAfterDelay(object localTalkingStick)
    {
        while (true)
        {
            await Task.Delay(5000);
            if (localTalkingStick != talkingStickWarnings || !warnings.Any()) return;
            warnings.RemoveAt(0);
        }
    }
}

Also I couldn't think of a better word for the talking sticks. They're not exactly locks.

Update: No more talking sticks, it now just reused the last message it received.

Update 2: Talking sticks again, as string have no unique identity.

Update 3: Instead of an extra event, the alerts are now in ObservableCollections. I keep receiving stuff about encapsulation and Rx. ould appreciate someone helping me to DRY this.

\$\endgroup\$
1
\$\begingroup\$

Try this collection to hold your messages. It should do exactly what you are looking for - just create couple instances for errors and warnings.

public class MessageCollection : ObservableCollection<string>
{
    public MessageCollection()
    {
        Timer = new Timer(Callback, this, 5000, 5000);
        CollectionChanged += MessageCollection_CollectionChanged;
    }

    Timer Timer { get; }

    void MessageCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Timer.Change(5000, 5000);
    }
    static void Callback(object state)
    {
        var that = (MessageCollection)state;
        if (that.Any())
        {
            that.CollectionChanged -= that.MessageCollection_CollectionChanged;
            that.RemoveAt(that.Count - 1);
            that.CollectionChanged += that.MessageCollection_CollectionChanged;
        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Ha I like it, will try this out. Wouldn't the RemoveAt(Count-1) remove the latest message though? I put RemoteAt(0) to always remove the oldest message. Also: why does it have to remove and readd the event handler? Isn't it the same object each iteration? \$\endgroup\$ Dec 28 '20 at 11:06
  • \$\begingroup\$ Ah also which Timer is this? I haven't found an async timer yet, and as this is running in a browser it definitely needs to be async. \$\endgroup\$ Dec 28 '20 at 11:11
  • \$\begingroup\$ MS just tells me both Timers.Timer and Threading.Timer are supposed to be used server-based and the others are just net Framework lol I feel a bit lost. Might have to build my own async timer. \$\endgroup\$ Dec 28 '20 at 12:12
  • \$\begingroup\$ Tried it, it works great, thanks! (I'm using the System.Threading.Timer) Also I understand now that the callback removes/readds the callback so that the change doesn't trigger resetting the timer, and that the callback is static so it has to use that. \$\endgroup\$ Jan 2 '21 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.