The purpose of this class is to efficiently notify another thread when data is available with minimal blocking to access/pass the data. The data reader/producer thread processes data in bulk which contains many objects. Instead of dispatching each object individually the objects are immediately or periodically pushed into a vector, it invokes an asynchronous notification and then continues to add objects to the vector. This allows the thread interested in the objects the ability to not have to poll for the objects and abstract itself away from the object creation. An example would be reading a continuous stream of data off the network where objects randomly arrive in different amounts and the objects get passed to another thread for processing. The goal is to minimize blocking and not miss a notification and leave an object in the vector. I know the .Net framework is vast so I may be reinventing the wheel. Have at it...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Remoting.Messaging;
namespace CoreObjects
{
public class NotifyVector<T>
{
public delegate void NotifyCBR();
private NotifyCBR notifyCbr_ = null;
private System.Collections.Generic.List<T> vector_;
private Object dataSyncObject_ = null;
private volatile bool processing_ = false;
private IAsyncResult ar_ = null;
// Class Instantiation requires a Callback routine
private NotifyVector() { }
public NotifyVector(NotifyCBR cbr)
{
notifyCbr_ = cbr;
vector_ = new System.Collections.Generic.List<T>();
dataSyncObject_ = new System.Object();
}
// This member fuction invokes the callback routine
public void Notify()
{
if (processing_ == false)
{
if (notifyCbr_ != null)
{
processing_ = true;
ar_ = notifyCbr_.BeginInvoke(new AsyncCallback(this.EndNotify), null);
}
}
}
// Function used to implement the Async Notification/Callback
void EndNotify(IAsyncResult ar)
{
// Retrieve the delegate.
AsyncResult result = (AsyncResult)ar;
NotifyCBR caller = (NotifyCBR)result.AsyncDelegate;
// Call EndInvoke to complete/cleanup Async call
caller.EndInvoke(ar_);
processing_ = false;
}
// Threadsafe add Object to vector
public void AddObject(T obj)
{
lock (dataSyncObject_)
{
vector_.Add(obj);
}
}
// Threadsafe pop Objects from vector
public bool PopObjects(ref System.Collections.Generic.List<T> inlist)
{
bool retval = false;
lock (dataSyncObject_)
{
if (vector_.Count() > 0)
{
inlist = vector_;
vector_ = new System.Collections.Generic.List<T>();
retval = true;
}
}
return retval;
}
}
}
I use VS17 Community and my unit test is as follows:
using CoreObjects;
namespace UnitTests.CoreObjectTests
{
public class Foo
{
private Foo() { }
public Foo(string s) { data_ = s; }
public string data_ { get; private set; }
}
[TestClass]
public class NotifyVectorTests
{
public NotifyVector<Foo> collectedFoo_;
private int fooCount_;
public void CallbackRoutine()
{
System.Collections.Generic.List<Foo> fooList = null;
while (collectedFoo_.PopObjects(ref fooList)) // vector handed over
{
for (int i = 0; i < fooList.Count; i++) // Do some work
fooCount_ += 1;
fooList.Clear();
}
fooList = null;
}
[TestMethod]
public void ConstructAndNotify()
{
fooCount_ = 0;
collectedFoo_ = new NotifyVector<Foo>(new
NotifyVector<Foo>.NotifyCBR(this.CallbackRoutine));
collectedFoo_.AddObject(new Foo("One"));
collectedFoo_.AddObject(new Foo("Two"));
collectedFoo_.AddObject(new Foo("Three"));
collectedFoo_.Notify(); // Performs the AsyncCallback
Assert.AreEqual(fooCount_, 0);
System.Threading.Thread.Sleep(1);
Assert.AreEqual(fooCount_, 3);
}
}
}
readonly
keyword could be useful to help show intention of your member variables. For example,notifyCbr_
only gets assigned in the constructor and appears to be a good candidate forreadonly
. This way someone can come into your class and see that it isn't assigned anywhere but the constructor and more importantly it will be obvious that members withoutreadonly
are assigned in places other than the constructor. \$\endgroup\$ConcurrentQueue
? \$\endgroup\$