Create a context group in C# to bind several operations into one - c#

I need to spawn individual actions that are not aware of each other but can be grouped into something like an execution group. The individual actions are also cascaded so that there is also more than one hierarchy level.
I can only show a small example to make things a bit clearer and open this example as a starting point for discussions.The example does not make complete sense, but it is to show what my intentions are.
In the example I want to start several algorithms that spawn other algorithms. Before I encapsulate the algorithms in an some sort of a context that binds all algorithms that were allocated in this context and adds them to the list of this context. At a certain time it could happen that someone wants to terminate all algorithms the Terminate() method can be called which calls the Termimnate() method on all algorithms in that context. Of course to make this work the algorithms need knowledge of the AlgorithmContext structure.
Another question is how do I transport the AlgorithmContext to the next thread or task? It also need to be threadsafe.
In the case of this example you can argue that I could hand over the CancellationToken down in the hierarchy of the algorithms but please keep in mind this is only an example. If was thinking in the direction of an ExecutionContext but maybe there are better approaches?
public class Algorithm
{
public readonly AlgorithmContext Context;
public Algorithm()
{
Context = AlgorithmContext.Current;
}
public void Terminate() { }
}
public class SmoothImage : Algorithm { }
public class SharpenImage : Algorithm { }
public class Example
{
public void Process(float[] imagedata)
{
// Everyone below should see the AlgorithmContext.Current until the end of the using.
// No one shall be able to interfere the Current inside the braces for example if another
// thread creates another instance of AlgorithmContext.
var Task1 = Task.Run(() =>
{
// The AlgorithmContext.Current shall be Context 1 until the end of the using braces.
using (var ctx = new AlgorithmContext("Context 1"))
{
CancellationTokenSource ct = new CancellationTokenSource();
ct.Token.Register(() => ctx.Terminate());
SmoothImage smoothImage = new SmoothImage();
SharpenImage sharpenImage = new SharpenImage();
}
});
var Task2 = Task.Run(() =>
{
// The AlgorithmContext.Current shall be Context 2 until the end of the using braces.
using (var ctx = new AlgorithmContext("Context 2"))
{
CancellationTokenSource ct = new CancellationTokenSource();
ct.Token.Register(() => ctx.Terminate());
SmoothImage smoothImage = new SmoothImage();
SharpenImage sharpenImage = new SharpenImage();
}
});
var Task3 = Task.Run(() =>
{
// The AlgorithmContext.Current shall be Context 2 until the end of the using braces.
using (var ctx = new AlgorithmContext("Context 3"))
{
var Task4 = Task.Run(() =>
{
// The AlgorithmContext.Current shall be Context 3
CancellationTokenSource ct = new CancellationTokenSource();
ct.Token.Register(() => ctx.Terminate());
SmoothImage smoothImage = new SmoothImage();
SharpenImage sharpenImage = new SharpenImage();
});
}
});
// There is no current context outside here at all times.
Task.WaitAll(Task1, Task2, Task3);
}
}
public class AlgorithmContext : IDisposable
{
List<Algorithm> Algorithms = new List<Algorithm>();
public readonly string Name;
[ThreadStatic]
public static AlgorithmContext Current;
public AlgorithmContext(string name)
{
Name = name;
Current = this;
}
public void Add(Algorithm algorithm) { Algorithms.Add(algorithm); }
public void Terminate()
{
foreach (var algo in Algorithms) algo.Terminate();
Algorithms.Clear();
}
public void Dispose()
{
Current = null;
}
}

I guess it's not as complicated as you described.
You need a context for multiple algorithm. One of the main jobs of this context is to handle termination of algorithms.
So, you need events! I rewrite your entire sample code.
public abstract class Algorithm
{
protected readonly AlgorithmContext _context;
public Algorithm(AlgorithmContext context)
{
_context = context;
_context.Terminated += (sender, e) =>
{
Terminate();
};
}
public abstract void Terminate();
}
public class SmoothImage : Algorithm
{
public SmoothImage(AlgorithmContext context) : base(context)
{
}
public override void Terminate()
{
// do whatever you want
}
}
public class SharpenImage : Algorithm
{
public SharpenImage(AlgorithmContext context) : base(context)
{
}
public override void Terminate()
{
// do whatever you want
}
}
public class Example
{
public void Process(float[] imagedata)
{
var Task1 = Task.Run(() =>
{
using (var ctx = new AlgorithmContext("Context 1"))
{
SmoothImage smoothImage = new SmoothImage(ctx);
SharpenImage sharpenImage = new SharpenImage(ctx);
}
});
// ...
Task.WaitAll(Task1, Task2, Task3);
}
}
public class AlgorithmContext : IDisposable
{
public AlgorithmContext(string name)
{
Name = name;
}
public event EventHandler Terminated;
public string Name { get; }
public void Dispose()
{
Terminate();
}
public void Terminate()
{
Terminated?.Invoke(this, EventArgs.Empty);
}
}
As you said, because algorithms are not aware of each other, so maybe they don't need to be in context.
If you need to terminate every other algorithms within a context, so just call _context.Terminate() somewhere in an algorithm
Anyway, hope it helps

Related

Returning Disposable from Subscribe method for an Observable

I have a question on Observables (which I posted on the publishers sub forum for this book but I am still waiting on any response).
I use the helper methods provided as is the standard practice rather than handcrafting the observables. However just out of academic interest I did see into what it takes to handcraft an observable.
I saw an implementation in a book where at the end of subscribe method Disposable.Empty was returned.
The code is somewhat like below.
public class MyObservable : IObservable<int>
{
public IDisposable Subscribe(IObserver<int> observer)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
observer.OnNext(i);
}
observer.OnCompleted();
return Disposable.Empty;
}
}
If I want to return a proper Disposable which will actually lead to unsubscribing when Dispose is called what should be the way?
I had a crack at it using this for the Observable and this for Observer
I had to introduce a subscription handler
public class SubscriptionHandler : IDisposable
{
private readonly List<IObserver<int>> _listOfObservers;
private readonly IObserver<int> _currentObserver;
public SubscriptionHandler(List<IObserver<int>> currentListOfObservers, IObserver<int> currentObserver)
{
_listOfObservers = currentListOfObservers;
_currentObserver = currentObserver;
}
public void Dispose()
{
if (_currentObserver != null && _listOfObservers.Contains(_currentObserver))
{
_listOfObservers.Remove(_currentObserver);
}
}
}
This is the code for the Observable
public class MyObservable : IObservable<int>
{
private List<IObserver<int>> _listOfSubscribedObservers = new List<IObserver<int>>();
public IDisposable Subscribe(IObserver<int> observer)
{
if (!_listOfSubscribedObservers.Contains(observer))
{
_listOfSubscribedObservers.Add(observer);
}
Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
observer.OnNext(i);
}
observer.OnCompleted();
});
return new SubscriptionHandler(_listOfSubscribedObservers, observer);
}
}
I have a feeling that I am missing something. There has to be a built in way to return a meaningful Disposable for handcrafted Observable or this is something which comes only with Observable create helper methods?
I should make clear that all of this is a demonstration of Rx design internals. You can have a look at classes AnonymousObservable<T>,
AnonymousObserver<T>, and AnonymousDisposable, which is how the framework does it. Pretty straight forward. However, you should almost never use any of this code, rather use things like Disposable.Create and Observable.Create. If you're implementing an IObservable, you're almost definitely doing it wrong.
Here's the basic idea: The observable needs to produce an IDisposable which removes the relevant observer from the observable's internal list of observers. Your code is (wrongly) removing all observers from the internal list.
Here's a basic disposable which makes it easy to create functionally. With this code, GenericDisposable.Create is the same as Disposable.Create(Action a).
public class GenericDisposable : IDisposable
{
public static IDisposable Create(Action disposeAction)
{
return new GenericDisposable(disposeAction);
}
private readonly Action _disposeAction;
public GenericDisposable(Action disposeAction)
{
_disposeAction = disposeAction;
}
public void Dispose()
{
_disposeAction();
}
}
...and here's an example observable implementation:
public class SendIntMessages : IObservable<int>
{
private readonly HashSet<IObserver<int>> _observers = new HashSet<IObserver<int>>();
protected void OnNext(int i)
{
foreach (var o in _observers)
o.OnNext(i);
}
protected void OnError(Exception e)
{
foreach (var o in _observers)
o.OnError(e);
}
protected void OnCompleted()
{
foreach (var o in _observers)
o.OnCompleted();
}
public void SendIntMessage(int i)
{
OnNext(i);
}
public void EndStream()
{
OnCompleted();
}
public void SendError(Exception e)
{
OnError(e);
}
public IDisposable Subscribe(IObserver<int> observer)
{
_observers.Add(observer);
return GenericDisposable.Create(() => _observers.Remove(observer));
}
}
This is a long-running, hot observable. It keeps track of its observers, and the disposable unsubscribes them.
Consider in contrast this observable:
public class CountTo5 : IObservable<int>
{
public IDisposable Subscribe(IObserver<int> observer)
{
observer.OnNext(1);
observer.OnNext(2);
observer.OnNext(3);
observer.OnNext(4);
observer.OnNext(5);
return GenericDisposable.Create(() => {});
}
}
This is a 'cold' observable that runs immediately. There's no way to unsubscribe in the middle: By the time you get the disposable, the observable has concluded.
Disposable.Empty is a simple short hand for DisposableCreate(() => {}).
To return a meaningful IDisposable to the caller, you should not generate all notifications synchronously during the subscription. You should generate them asynchronously on a different context, and return to the caller immediately a subscription that has not completed yet. Here is a way to do it, by using the Task.Run method to invoke the notifications on the ThreadPool:
public class MyObservable : IObservable<int>
{
public IDisposable Subscribe(IObserver<int> observer)
{
var cts = new CancellationTokenSource();
_ = Task.Run(async () =>
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000, cts.Token);
observer.OnNext(i);
}
observer.OnCompleted();
}, cts.Token);
return new CancellationDisposable(cts);
}
}
The CancellationDisposable class...
Represents a disposable resource that has an associated CancellationToken that will be set to the cancellation requested state upon disposal.

Roslyn CodeFixProvider: Move caret after applying code fix

i have implemented a custom CodeFixProvider that adds some XML documentation to members.
Example:
public void MyMethod() { }
will be transformed to
/// <summary></summary>
public void MyMethod() { }
The CodeFixProvider is implemented like this:
public class MyCodeFixProvider : CodeFixProvider
{
...
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
await Task.Run(() =>
{
Diagnostics diagnostics = context.Diagnostics.First();
CodeAction codeFix = CodeAction.Create("Title", c => CreateXmlDocs(...));
context.RegisterCodeFix(codeFix, diagnostics);
}
).ConfigureAwait(false);
}
...
}
Everything is working like expected.
Now i want to add some extra functionality: After applying the code fix, the caret should be moved inside the empty summary tag.
I discovered the DocumentNavigationOperation class included in Microsoft.CodeAnalysis.Features NuGet package. This class should be able to move the caret to a specified position. But I can't find any instructions how to use this class. If i call it from inside my CreateXmlDocs method, an exception is thrown:
Navigation must be performed on the foreground thread.
Code:
private static async Task<Solution> CreateXmlDocs()
{
...
new DocumentNavigationOperation(newDocument.Id, 42)
.Apply(newDocument.Project.Solution.Workspace, cancellationToken);
...
}
I'm not sure if it makes sense to use this class inside my CreateXmlDocs method, because the new solution created inside this method isn't yet applied by Visual Studio when DocumentNavigationOperation is called.
Does anybody knows a solution to move the caret after applying a code fix?
Ok, in the meantime i found a solution for this.
A custom CodeAction is required to get it working:
internal class NavigateAfterCodeChangeAction : CodeAction
{
private readonly Func<CancellationToken, Task<Solution>> codeChangeOperation;
private readonly Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation;
public NavigateAfterCodeChangeAction(
string title,
Func<CancellationToken, Task<Solution>> codeChangeOperation,
Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation)
{
this.Title = title;
this.codeChangeOperation = codeChangeOperation;
this.navigationTargetCalculation = navigationTargetCalculation;
}
public override string Title { get; }
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
{
var operations = new List<CodeActionOperation>();
Solution changedSolution = await this.codeChangeOperation(cancellationToken);
NavigationTarget navigationTarget = await this.navigationTargetCalculation(changedSolution, cancellationToken);
operations.Add(new ApplyChangesOperation(changedSolution));
if (navigationTarget != null)
{
operations.Add(new DocumentNavigationOperation(navigationTarget.DocumentId, navigationTarget.Position));
}
return operations;
}
}
internal class NavigationTarget
{
public NavigationTarget(DocumentId documentId, int position)
{
this.DocumentId = documentId;
this.Position = position;
}
public DocumentId DocumentId { get; }
public int Position { get; }
}
The new CodeAction can be used in the CodeFixProvider instead of CodeAction.Create():
public class MyCodeFixProvider : CodeFixProvider
{
...
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
await Task.Run(() =>
{
Diagnostics diagnostics = context.Diagnostics.First();
CodeAction codeFix = new NavigateAfterCodeChangeAction(
"Title",
c => CreateXmlDocs(...)
(s, c) => CalculateNavigationTarget(context.Document));
context.RegisterCodeFix(codeFix, diagnostics);
}
).ConfigureAwait(false);
}
private static NavigationTarget CalculateNavigationTarget(Document doc)
{
// Calculate the navigation target here...
// Example: Navigate to position 42 of the document
return new NavigationTarget(doc.Id, 42);
}
...
}

How to register an event call a delegated method in a different class using an extension method in .NET Core?

I'm trying to achieve async data movement between instances of different classes without actually using class references and building an extension method for their base classes instead.
It's a .NET Core Class Library, targeting .NET Standard 1.6.
Let's say I have a class with an async void method that continuously updates a property of the same class:
public abstract class DataRetriever : DataRetrieverAbstract
{
public int CollectionInterval { get; private set; }
public float DataResult { get; private set; }
private float ReadData()
{
return 1; //in reality it returns different values every time
}
public async void StartReading(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(CollectionInterval * 1000);
DataResult = ReadData();
}
}
}
I also have a second class with an async void method which writes the data somewhere:
public abstract class DataWriter : DataWriterAbstract
{
public async void WriteData(float dataToWrite)
{
// some writing magic
}
}
How can I build an extension method that basically is going to "pair" two or more instances of these classes together and delegate the events?
Something like this:
public static DataRetrieverAbstract PairToWriter (this DataRetrieverAbstract retriever, DataWriterAbstract writer)
{
// ???
}
To then use it like this:
var dataRetriever1 = new DataRetriever();
var dataRetriever2 = new DataRetriever();
var dataRetriever3 = new DataRetriever();
var dataWriter1 = new DataWriter();
var dataWriter2 = new DataWriter();
dataRetriever1.PairToWriter(dataWriter1).PairToWriter(dataWriter2);
dataRetriever3.PairToWriter(dataWriter2);
// ... stuff goes on
dataRetriever1.StartReading(token);
dataRetriever2.StartReading(token);
dataRetriever3.StartReading(token);
So basically we have one Retriever writing into two different instances of DataWriter and 3-rd Retriever using just the second instance.
What is the best way to do it?
The async void stuff is a bit of a red herring, but you could achieve this using an event with multiple subscribers. Internally, this is similar to storing instances of the reader in a list inside the writer. Here's an example:
class Program
{
class DataRetriever
{
public event Action<float> DataReady;
private float ReadData() => 1;
public async Task StartReading()
{
while (true)
{
await Task.Delay(1000);
DataReady?.Invoke(ReadData());
}
}
}
class DataWriter
{
public void WriteData(float dataToWrite)
{
Console.WriteLine(dataToWrite);
}
}
static void Main(string[] args)
{
var reader1 = new DataRetriever();
var reader2 = new DataRetriever();
var reader3 = new DataRetriever();
var writer1 = new DataWriter();
var writer2 = new DataWriter();
reader1.DataReady += writer1.WriteData;
reader2.DataReady += writer2.WriteData;
reader3.DataReady += writer2.WriteData;
Task.Run(reader1.StartReading);
Task.Run(reader2.StartReading);
Task.Run(reader3.StartReading);
Console.ReadKey();
}
}
You just need to add some event to base abstract class and subscribe to it. By the way, I suggest to use Task instead of void, because your read/write method can be non completed when your app is finished, so you doesn't "see" the result of these methods:
Here it's DataRetrieverAbstract and his derrived class:
public abstract class DataRetrieverAbstract
{
public virtual event Action<float> DataReaded;
protected void FireDataReaded(float arg)
{
DataReaded?.Invoke(arg);
}
}
public class DataRetriever : DataRetrieverAbstract
{
public int CollectionInterval { get; set; }
public float DataResult { get; private set; }
private float ReadData()
{
return 1; //in reality it returns different values every time
}
public async Task StartReading(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(CollectionInterval * 1000);
DataResult = ReadData();
}
FireDataReaded(DataResult);
}
}
Next you should move WriteData to base class if you want to subscribe on event using base abstract class not derived:
public abstract class DataWriterAbstract
{
public abstract void WriteData(float dataToWrite);
}
public class DataWriter : DataWriterAbstract
{
public override void WriteData(float dataToWrite)
{
// some writing magic
Console.WriteLine(dataToWrite);
}
}
So your extension is very simple:
public static DataRetrieverAbstract SubscribeOnReaded(this DataRetrieverAbstract retriever, DataWriterAbstract writer)
{
retriever.DataReaded += writer.WriteData;
return retriever;
}
And usage:
var dataRetriever1 = new DataRetriever() { CollectionInterval = 2 };
var dataRetriever2 = new DataRetriever() { CollectionInterval = 3 };
var dataRetriever3 = new DataRetriever() { CollectionInterval = 4 };
var dataWriter1 = new DataWriter();
var dataWriter2 = new DataWriter();
dataRetriever1.SubscribeOnReaded(dataWriter1).SubscribeOnReaded(dataWriter2);
dataRetriever3.SubscribeOnReaded(dataWriter2);
//...
CancellationTokenSource source = new CancellationTokenSource();
var tasks = new[] { dataRetriever1.StartReading(source.Token), dataRetriever2.StartReading(source.Token), dataRetriever3.StartReading(source.Token) };
source.Cancel();
// If you want to wait a tasks results – uncomment the line below
//Task.WaitAll(tasks);

Making asynchronous API synchronous

I am connecting to an API to get some data that is defined like this:
A client object ClientConnection, which allows one to send requests.
A IApi interface that needs to be passed to the ClientConnection to receive callbacks.
Schematically it looks like this:
// defined in the API dll
public class ClientConnection {
public ClientConnection(IApi api) { ... }
public void request(int reqid, string reqdetails) { ... }
}
interface IApi
{
void receiveData(int reqid, string ans);
}
Now, obviously this is a fairly standard asynchronous way of doing things: send requests through a global object, with a requestid, and receive answers tagged with that requestid.
I want to create a wrapper that is synchronous. What would be the most natural way of doing this? Is there a smart way of using async await, instead of using thread locking and stuff?
class MyWrapper : IApi
{
private ClientConnection _client;
private int _reqToken = 0;
public MyWrapper()
{
_client = new ClientConnection(this);
}
public string getData(string reqdetails)
{
_client.request(_reqToken++, reqdetails);
// what to do here?
}
public void receiveData(int reqid, string data) {
// what to do here?
}
}
Didn't test the code below, but it should give you the idea. Basically you can use ManualResetEvent to be signalled when you receive your result (and don't ever call this without proper timeout):
class MyWrapper : IApi {
private ClientConnection _client;
// here you store your requests
private Dictionary<int, PendingRequest> _pendingRequests = new Dictionary<int, PendingRequest>();
private int _reqToken = 0;
public MyWrapper() {
_client = new ClientConnection(this);
}
public string getData(string reqdetails, TimeSpan timout) {
// if this is multithreaded - lock over _pendingRequests when you add\remove requests there
// and when you increment your _reqToken, or use concurrent collection
using (var token = new PendingRequest()) {
var id = _reqToken;
// lock here
_pendingRequests.Add(id, token);
_client.request(id, reqdetails);
// and here use Interlocked.Increment
_reqToken++;
if (!token.Signal.WaitOne(timout)) {
// and here
_pendingRequests.Remove(id);
// timeout
throw new Exception("timout");
}
// if we are here - we have the result
return token.Result;
}
}
public void receiveData(int reqid, string data) {
// here you might need to lock too
if (_pendingRequests.ContainsKey(reqid)) {
var token = _pendingRequests[reqid];
_pendingRequests.Remove(reqid);
token.Complete(data);
}
}
private class PendingRequest : IDisposable {
public PendingRequest() {
Signal = new ManualResetEvent(false);
}
public ManualResetEvent Signal { get; private set; }
public string Result { get; private set; }
public void Complete(string result) {
this.Result = result;
Signal.Set();
}
public void Dispose() {
Signal.Dispose();
}
}
}

Multithreading BlockingCollection Alternatives to GetConsumingEnumerable() Producer-Consumer

I have a situation where I have multiple producers and multiple consumers. The producers enters a job into a queue. I chose the BlockingCollection and it works great since I need the consumers to wait for a job to be found. However, if I use the GetConsumingEnumerable() feature the order of the items in the collection change... this is not what I need.
It even says in MSDN http://msdn.microsoft.com/en-us/library/dd287186.aspx
that it does not preserve the order of the items.
Does anyone know an alternative for this situation?
I see that the Take method is available but does it also provide a 'wait' condition for the consumer threads?
It says http://msdn.microsoft.com/en-us/library/dd287085.aspx
'A call to Take may block until an item is available to be removed.' Is it better to use TryTake? I really need the thread to wait and keep checking for a job.
Take blocks the thread till something comes available.
TryTake as the name implies tries to do so but returns a bool if it fails or succeeds.
Allowing for more flex using it:
while(goingOn){
if( q.TryTake(out var){
Process(var)
}
else{
DoSomething_Usefull_OrNotUseFull_OrEvenSleep();
}
}
instead of
while(goingOn){
if( var x = q.Take(){
//w'll wait till this ever will happen and then we:
Process(var)
}
}
My votes are for TryTake :-)
EXAMPLE:
public class ProducerConsumer<T> {
public struct Message {
public T Data;
}
private readonly ThreadRunner _producer;
private readonly ThreadRunner _consumer;
public ProducerConsumer(Func<T> produce, Action<T> consume) {
var q = new BlockingCollection<Message>();
_producer = new Producer(produce,q);
_consumer = new Consumer(consume,q);
}
public void Start() {
_producer.Run();
_consumer.Run();
}
public void Stop() {
_producer.Stop();
_consumer.Stop();
}
private class Producer : ThreadRunner {
public Producer(Func<T> produce, BlockingCollection<Message> q) : base(q) {
_produce = produce;
}
private readonly Func<T> _produce;
public override void Worker() {
try {
while (KeepRunning) {
var item = _produce();
MessageQ.TryAdd(new Message{Data = item});
}
}
catch (ThreadInterruptedException) {
WasInterrupted = true;
}
}
}
public abstract class ThreadRunner {
protected readonly BlockingCollection<Message> MessageQ;
protected ThreadRunner(BlockingCollection<Message> q) {
MessageQ = q;
}
protected Thread Runner;
protected bool KeepRunning = true;
public bool WasInterrupted;
public abstract void Worker();
public void Run() {
Runner = new Thread(Worker);
Runner.Start();
}
public void Stop() {
KeepRunning = false;
Runner.Interrupt();
Runner.Join();
}
}
class Consumer : ThreadRunner {
private readonly Action<T> _consume;
public Consumer(Action<T> consume,BlockingCollection<Message> q) : base(q) {
_consume = consume;
}
public override void Worker() {
try {
while (KeepRunning) {
Message message;
if (MessageQ.TryTake(out message, TimeSpan.FromMilliseconds(100))) {
_consume(message.Data);
}
else {
//There's nothing in the Q so I have some spare time...
//Excellent moment to update my statisics or update some history to logfiles
//for now we sleep:
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
}
}
catch (ThreadInterruptedException) {
WasInterrupted = true;
}
}
}
}
}
USAGE:
[Fact]
public void ConsumerShouldConsume() {
var produced = 0;
var consumed = 0;
Func<int> produce = () => {
Thread.Sleep(TimeSpan.FromMilliseconds(100));
produced++;
return new Random(2).Next(1000);
};
Action<int> consume = c => { consumed++; };
var t = new ProducerConsumer<int>(produce, consume);
t.Start();
Thread.Sleep(TimeSpan.FromSeconds(5));
t.Stop();
Assert.InRange(produced,40,60);
Assert.InRange(consumed, 40, 60);
}

Categories