0

I implemented an observer pattern using events and delegates. The program is receiving and processing big amounts of data (around 3000 messages per second) but at some point, it starts sending messages with a delayed timestamp, which I am trying to fix. I have 3 main classes that do the job in my opinion:

public class MessageTracker : IObservable<MessageEventArgs>
{
       private List<IObserver<MessageEventArgs>> observers;

       public MessageTracker()
       {
           observers = new List<IObserver<MessageEventArgs>>();
       }

       private static readonly MessageTracker mInstance = new MessageTracker();
       private static MessageTracker getInstance() => mInstance;

private class Unsubscriber : IDisposable
{
       private List<IObserver<MessageEventArgs>> _observers;
       private IObserver<MessageEventArgs> _observer;

       public Unsubscriber(List<IObserver<MessageEventArgs>> observers, IObserver<MessageEventArgs> observer)
       {
           this._observers = observers;
           this._observer = observer;
       }

       public void Dispose() 
       {
           if (! (_observer == null)) _observers.Remove(_observer);
       }
}

       public IDisposable Subscribe(IObserver<MessageEventArgs> observer)
       {
           if (! observers.Contains(observer))
           observers.Add(observer);

           return new Unsubscriber(observers, observer);
       }

       public void MessageTrack(MessageEventArgs msg) {
           observers.AsParallel().ForAll(observer =>
       {
           if (msg is null) 
              observer.OnError(new ArgumentException("MessageError."));
           else
              observer.OnNext(msg);
       });
       }

       public void EndMessageTrans(){
           foreach(var observer in observers.ToArray())
           if (observers.Contains(observer))
                observer.OnCompleted();

           observers.Clear();
       }
}



public class MessageReporter : IObserver<MessageEventArgs>
{
    private IDisposable unsubscriber;

    public MessageReporter()
    { }

    public event EventHandler<MessageEventArgs> OnNextMessage;


    public virtual void Subscribe(IObservable<MessageEventArgs> provider)
    {
        if (provider != null)
            unsubscriber = provider.Subscribe(this);
    }

    public void OnCompleted()
    {
        this.Unsubscribe();
    }

    public void OnError(Exception error)
    {
    }

    public void OnNext(MessageEventArgs value)
    {
        if (OnNextMessage != null)
        {
            OnNextMessage?.Invoke(this, value);
        }
    }

    public virtual void Unsubscribe()
    {
        unsubscriber.Dispose();
    }
}
public sealed class MessageDataWorker 
{
    private readonly bool mSubscribeAll;
    private readonly IEnumerable<string> mMessages;

    public MessageDataWorker(IEnumerable<string> messages)
    {
        mMessages = messages;

        if ((mMessages?.Count() ?? 0) == 0)
            mSubscribeAll = true;
    }

    public override void DoWork()
    {


        var messageReporter = new MessageReporter();
        messageReporter.OnNextMessage += OnNewMessageReceived;
        messageReporter.Subscribe(MessageTracker.GetInstance());

        while (!mShouldStop.WaitOne(100)) ;
        MessageReporter.Unsubscribe();
    }

    private void OnNewMessageReceived(object sender, MessageEventArgs e)
    {
        if (!mSubscribeAll && !mMessages.Contains(e.Message))
            return;
        string message = "Message|" +
                        $"{e.Time}|" +
                        $"{e.Text};

        try
        {
            Console.WriteLine(message);
        }
        catch { }
    }
}

What I am trying to achieve is skipping notifications or receiving data for X milliseconds after sending the last message and afterward send the newest received message. I tried sleeping the observers and the provider but it just increased the delay. I think I am missing something and any suggestion would be appreciated.

dapi
  • 1
  • 1
  • *"it starts sending messages with a delayed timestamp"* - you can add `DateTime.Now` as event argument and invoking event via queue (see e.g [TaskQueue](https://stackoverflow.com/a/32993768/1997232)), this way event may be rised and handled much later, but timestamp will be accurate. – Sinatr Feb 21 '20 at 08:49
  • Most probably you flooded the observer and the messages waits to be processed. The delay is because the there is no resources to process it at the moment. – Azzy Elvul Feb 21 '20 at 08:57
  • @Sinatr I might have misunderstood you but I am receiving the messages with a timestamp, which I cannot update with `DateTime.Now`. The message timestamp and local timestamp should match after processing it. – dapi Feb 21 '20 at 09:30
  • @AzzyElvul Yes, that might be the reason. That is why I am trying to skip the data recieved for X milliseconds. – dapi Feb 21 '20 at 15:06
  • Please don't ever implement your own observable class like this. It's fraught with danger. If you could please provide a [mcve] I can show you how to do it properly. Your code, at the moment, doesn't compile. It's got basic errors, missing methods, and even missing classes. – Enigmativity Feb 27 '20 at 00:22
  • @Enigmativity Can you, please, give more details for the dangers of implementing it like that? The example is almost the same like the one given here (https://learn.microsoft.com/en-us/dotnet/standard/events/how-to-implement-a-provider) – dapi Feb 27 '20 at 13:02
  • @dapi - There are all sorts of issues with things like a rogue observer locking down the observable, or issues with multithreading causing the signature of the Rx contract to be broken. It's not something that I would do. At most I would use the in-built operators and wrap a class around it. – Enigmativity Feb 27 '20 at 21:35
  • @dapi - Oh, and the implementation is not thread-safe. And that can be very bad with Rx as it is difficult to know what thread will call your code. – Enigmativity Feb 27 '20 at 21:45

1 Answers1

0

From what I can tell from your code you could write the three classes with this code:

var messageTrack = new Subject<MessageEventArgs>();

var query =
    from e in messageTrack
    where !mMessages.Contains(e.Message)
    select $"Message|{e.Time}|{e.Text}";

query.Throttle(TimeSpan.FromMilliseconds(X)).Subscribe(Console.WriteLine);

You should never need to implement IObservable<> or IObserver<> yourself. It almost always ends in disaster.

The above code handles the throttling you wanted.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172