Based on below answer, I implemented least loaded connection for StackExchange.Redis:
https://stackoverflow.com/a/58106770
public class RedisConnectionWrapper : IRedisConnectionWrapper
{
#region Fields
private readonly Config _config;
private bool _disposed = false;
private readonly Lazy<string> _connectionString;
private static ConcurrentBag<Lazy<ConnectionMultiplexer>> _connections;
#endregion
#region Ctor
public RedisConnectionWrapper(Config config)
{
_config = config;
_connectionString = new Lazy<string>("CONNECTION_STRING");
ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", _config.RedisPreventThreadTheft);
if (_config.UseLeastLoadedConnection)
InitializeLeastLoadedConnections();
}
#endregion
/// <summary>
/// Initialize lazy connections to Redis servers
/// </summary>
/// <returns></returns>
private void InitializeLeastLoadedConnections()
{
_connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();
for (var i = 0; i < _config.PoolSize; i++)
{
var connection = ConnectionMultiplexer.Connect(_connectionString.Value);
connection.IncludePerformanceCountersInExceptions = true;
_connections.Add(new Lazy<ConnectionMultiplexer>(connection));
}
}
/// <summary>
/// Get least loaded connection to Redis servers
/// </summary>
/// <returns></returns>
protected ConnectionMultiplexer GetLeastLoadedConnection()
{
Lazy<ConnectionMultiplexer> connection;
var loadedLazys = _connections.Where(lazy => lazy.IsValueCreated && lazy.Value.IsConnected);
if (loadedLazys.Count() == _connections.Count)
{
var minValue = _connections.Min(lazy => lazy.Value.GetCounters().TotalOutstanding);
connection = _connections.First(lazy => lazy.Value.GetCounters().TotalOutstanding == minValue);
}
else
{
Console.WriteLine("Creating a new connection to Redis");
connection = _connections.First(lazy => !lazy.IsValueCreated);
}
return connection.Value;
}
/// <summary>
/// Release all resources associated with this object
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
if (_config.UseLeastLoadedConnection)
{
var activeConnections = _connections.Where(lazy => lazy.IsValueCreated).ToList();
activeConnections.ForEach(connection => connection.Value.Dispose());
}
}
_disposed = true;
}
But I'm getting this exception on high traffic:
System.InvalidOperationException: Sequence contains no matching element
at System.Linq.ThrowHelper.ThrowNoMatchException()
at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate)
Anybody can help me?
After some traces, I did some changes and it worked:
var loadedLazys = _connections.Where(lazy => lazy.IsValueCreated);
if (loadedLazys.Count() == _connections.Count)
{
connection = _connections.OrderBy(lazy => lazy.Value.GetCounters().TotalOutstanding).First();
}
I changed this part of my code as well:
private void InitializeLeastLoadedConnections()
{
lock (_lock)
{
_connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();
for (var i = 0; i < _config.PoolSize; i++)
{
_connections.Add(new Lazy<ConnectionMultiplexer>(() =>
{
var connection = ConnectionMultiplexer.Connect(_connectionString.Value);
connection.IncludePerformanceCountersInExceptions = true;
return connection;
}));
}
}
}
Also I changed the return type of GetLeastLoadedConnection() from ConnectionMultiplexer to IConnectionMultiplexer.
Related
I am creating a pos app that cannot communicate with my fiscal printer. So I have decided to store a receipt in a text file as Json object and make Windows Service app with FileSystemWatch to check for file updates and forward it to printer. I am using third party library to communicate with the printer. Here is a code of the service:
Program.cs
static void Main(string[] args)
{
var program = new Watcher();
if (Environment.UserInteractive)
{
program.Start();
}
else
{
ServiceBase.Run(new ServiceBase[]
{
program
});
}
//ServiceBase[] ServicesToRun;
//ServicesToRun = new ServiceBase[]
//{
// new Watcher()
//};
//ServiceBase.Run(ServicesToRun);
}
Watcher.cs
public partial class Watcher : ServiceBase
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);
public static OICFiscalPrinter printer { get; set; }
[StructLayout(LayoutKind.Sequential)]
public struct ServiceStatus
{
public long dwServiceType;
public ServiceState dwCurrentState;
public long dwControlsAccepted;
public long dwWin32ExitCode;
public long dwServiceSpecificExitCode;
public long dwCheckPoint;
public long dwWaitHint;
};
public enum ServiceState
{
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007,
}
public Watcher()
{
InitializeComponent();
}
public void CheckReceipt(object e, FileSystemEventArgs args)
{
printer = new OICFiscalPrinter();
var name = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string text = null;
try
{
text = System.IO.File.ReadAllText(name + "\\Pictures\\test.txt");
var BasketList = JsonConvert.DeserializeObject<List<ItemsOnFacture>>(text);
printer.PortConfigString = "PortName=COM4;DataBits=8;Speed=9600;" +
"Parity = N; StopBits = 1; FlowControl = X;" +
"ReadTimeout = 6000;" +
"WriteTimeout = 500; UseReadBuffer = 1";
printer.Active = true;
var t = printer.Open();
if (!t) return;
printer.OpenReceipt();
foreach (var item in BasketList)
{
printer.ReceiptItem(item.ItemName, item.VatFee == 5 ? "B" : item.VatFee == 8 ? "A" : "D",
(decimal)item.PriceBrutto,
item.Amount, "unit", (decimal)item.PriceBruttoSum);
}
printer.CloseReceipt((decimal)BasketList.Sum(w => w.PriceBruttoSum),
(decimal)BasketList.Sum(w => w.PriceBruttoSum));
printer.Close();
File.Delete(name + "\\Pictures\\test.txt");
}
catch
{
}
}
public void Start()
{
//Start Logic here
var serviceStatus = new ServiceStatus
{
dwCurrentState = ServiceState.SERVICE_START_PENDING,
dwWaitHint = 100000
};
this.fileSystemWatcher1 = new System.IO.FileSystemWatcher();
((System.ComponentModel.ISupportInitialize)(this.fileSystemWatcher1)).BeginInit();
//
// fileSystemWatcher1
//
this.fileSystemWatcher1.EnableRaisingEvents = true;
var name = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
fileSystemWatcher1 = new FileSystemWatcher(name + "\\Pictures", "test.txt")
{
EnableRaisingEvents = true,
IncludeSubdirectories = false,
NotifyFilter = NotifyFilters.DirectoryName
};
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
this.fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler(this.CheckReceipt);
((System.ComponentModel.ISupportInitialize)(this.fileSystemWatcher1)).EndInit();
// Update the service state to Running.
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
}
protected override void OnStart(string[] args)
{
Start();
}
protected override void OnContinue()
{
}
protected override void OnStop()
{
}
private FileSystemWatcher fileSystemWatcher1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//
// Watcher
//
components = new System.ComponentModel.Container();
this.ServiceName = "WATTOFP";
}
#endregion
}
ProjectInstaller.cs
[RunInstaller(true)]
public class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
//
// serviceInstaller1
//
this.serviceInstaller1.Description = "WATTO Fiscal Printer";
this.serviceInstaller1.DisplayName = "WATTO Fiscal Printer";
this.serviceInstaller1.ServiceName = "WATTOFP";
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.serviceInstaller1});
}
#endregion
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
}
The problem is the fact that after installation when I try to run the service it starts and immediately stops as the warning appears. How can I make the service run and the watch the file for the changes?
The problem might be that service by default is running under system account. Thus user folder is not what you're expecting and there is no file there.
Usually when service cannot be started, there should be a error with exception in event log. Please post it here for further assistance.
I have SinglaR hub class "MyHub" and a .net client class that connects to it successfully in runtime. however when i'm testing the client with nunit test it doesn't connect, I've debugged the test and find that hub instance doesn't created by SinglaR's hub activator class.
I'm using Resharper nunit runner for running test.
Here is my hub class structure, i've remove method's body for simplicity.
[HubName("myHub")]
public class MyHub : Hub
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyHub));
#region MyRegion
#region Connection Methods
public MyHub()
{
}
private static int c;
public override Task OnConnected()
{
}
private string UserNameConnectionId
{
}
public override Task OnReconnected()
{
lock (LockObject)
{
}
}
public override Task OnDisconnected(bool b)
{
lock (LockObject)
{
}
}
private void LogUserActivity(string userName, string activity)
{
}
#endregion
//Live controls invoke this Method to send their required data and also their ConnectionId
/// <summary>
/// </summary>
/// <param name="liveControlsInfo"></param>
public Task UpdateLiveControlsInfo(LiveControlInfo[] liveControlsInfo)
{
lock (LockObject)
{
}
}
public string GetValue()
{
return "SignalR Rox!";
}
public void Acknowledge()
{
lock (LockObject)
{
string connectionId = Context.ConnectionId;
}
}
}
#endregion
}
}
here is HubWinClient class
public class HubWinClient
{
private readonly string _userName;
public int ServerPort { get; set; }
private static readonly ILog logger = LogManager.GetLogger(typeof (HubWinClient));
protected int HubLogCounter = 0;
private HubConnection connection;
private bool moduleStarted;
private IHubProxy myHub;
public HubWinClient(int serverPort, string userName= "Windows Hub Client", bool tryConnect=false)
{
_userName = userName;
ServerPort = serverPort;
}
public void Start()
{
ConnectToHub();
moduleStarted = true;
}
public void Stop()
{
DisconnectFromHub();
moduleStarted = false;
}
public void ConnectToHub()
{
ConnectToHub(false);
}
public event EventHandler<EventArgs> ConnectingToHub = delegate { };
public event EventHandler<EventArgs> ConnectedToHub = delegate { };
/// <summary>
/// callback event called by hubhost
/// </summary>
public event EventHandler<int> LogSentToHub = delegate { };
public event EventHandler<EventArgs> DisonnectedFromHub = delegate { };
public event EventHandler<EventArgs> HubConnectionError = delegate { };
public event EventHandler<EventArgs> HubInvokeError = delegate { };
public event EventHandler<EventArgs> HubInvokeSuccess = delegate { };
public event EventHandler<EventArgs> NotConnectedToSentLog = delegate { };
public event EventHandler<LogMessage> DataRecivedFromHub = delegate { };
/// <summary>
/// connects HunNotifier to Hun in order to ListenerMessage passing can be done
/// </summary>
/// <param name="reconnect"></param>
private void ConnectToHub(bool reconnect)
{
//This url will not point to a specific connection. But will instead point to the root of your site=root of hub server.
string hubAddress = "http://localhost:" + ServerPort;
connection = new HubConnection(hubAddress, new Dictionary<string, string>{{"UserName", _userName } });
ConnectingToHub(this, new EventArgs());
if (reconnect)
logger.InfoFormat("HubWinClient reconnecting to HubHost # {0}...", hubAddress);
else
logger.InfoFormat("HubWinClient connecting to HubHost # {0}...", hubAddress);
myHub = connection.CreateHubProxy("myHub");
myHub.On("joined", joined);
myHub.On("rejoined", rejoined);
myHub.On("leaved", leaved);
myHub.On<LogMessage>("addData", addData);
myHub.On<int>("LogSentToWeb", LogSentToWeb);
connection.StateChanged += change =>
{
var a = change;
var b = 10;
var a1 = connection.LastError;
var b2 = connection.Proxy;
};
try
{
connection.Start();
}
catch (AggregateException aggregateException)
{
Exception exception = aggregateException.InnerException;
logger.Error("HubWinClient connection to HubHost exception...", exception);
HubConnectionError(this, new MyEventArgs {Exception = exception});
}
catch (Exception exception)
{
logger.Error("HubWinClient connection to HubHost exception...", exception);
HubConnectionError(this, new MyEventArgs {Exception = exception});
}
}
public void DisconnectFromHub()
{
connection.Stop();
}
/// <summary>
/// when hub notifier connected to hub, hub invoke this method to inform connection success.
/// </summary>
public void joined()
{
var liveControlInfo = new LiveControlInfo
{
LiveControlType = LiveControlType.mainClient
};
// myHub.Invoke("AddLiveControl", liveControlInfo).Wait();
// logger.Info("HubWinClient joined");
ConnectedToHub(this, new EventArgs());
}
/// <summary>
/// when hub notifier reconnected to hub, hub invoke this method to inform reconnection success.
/// </summary>
public void rejoined()
{
logger.Info("HubWinClient rejoined");
}
/// <summary>
/// when hub notifier disconnect from hub, hub invoke this method to inform disconnection.
/// </summary>
public void leaved()
{
logger.Info("HubWinClient disconnected");
DisonnectedFromHub(this, new EventArgs());
}
public void addData(LogMessage logMessage)
{
DataRecivedFromHub(this, logMessage);
}
public event EventHandler<LogMessage> OnSendingLogToHub = delegate { };
/// <summary>
/// called by HubClientMoudle when it's ApplyLogReceived are invoked by WCF service
/// </summary>
/// <param name="logMessage"></param>
public void SendToHub(LogMessage logMessage)
{
OnSendingLogToHub(this, logMessage);
if (!moduleStarted) return;
if (connection.State != ConnectionState.Connected)
ConnectToHub(true);
if (connection.State == ConnectionState.Connected)
{
if (System.Diagnostics.Debugger.IsAttached ||
Math.Abs((logMessage.Log.ArrivalDateTime - DateTime.Now).Ticks) < TimeSpan.FromMinutes(10).Ticks)
{
try
{
Task task = myHub.Invoke("send", logMessage);
task.Wait(5000);
logger.Debug("Send log success");
HubInvokeSuccess(this, new EventArgs());
}
catch (AggregateException aggregateException)
{
Exception exception = aggregateException.InnerException;
logger.Error("Send log Exceotion!", exception);
HubInvokeError(this, new MyEventArgs {Exception = exception});
}
catch (Exception exception)
{
logger.Error("Send log Exceotion!", exception);
HubInvokeError(this, new MyEventArgs {Exception = exception});
}
}
else
{
logger.Warn("Log arrival date time is not less than DateTime.Now+30 minutues");
}
}
else
{
logger.Warn("HubWinClient not connected to send log");
NotConnectedToSentLog(this, new EventArgs());
}
}
/// <summary>
/// when Hub receives log from HubWinClient, calls this method to inform send success
/// </summary>
/// <param name="log"></param>
public void LogSentToWeb(int logId)
{
logger.Debug("Call back from Hub: send success");
LogSentToHub(this, logId);
}
}
Here is my test:
[Category("Integration")]
public class HubWinClientTests
{
[Test]
public void TestConnection()
{
//arrange
const int port = 4000;
HubHost hubHost = new HubHost(port);
HubWinClient hubWinClient = new HubWinClient(port,"mvc app client");
bool clientConnected = false;
bool clientDisconnected = false;
bool hubInvokeError = false;
hubWinClient.ConnectedToHub += (sender, args) => { clientConnected = true; };
hubWinClient.DisonnectedFromHub += (sender, args) => { clientDisconnected = true; };
hubWinClient.HubInvokeError += (sender, args) => { hubInvokeError = true; };
//act
hubHost.Start();
hubWinClient.Start();
//assert
var constrain = Is.True.After(10000, 100);
Assert.That(() => clientConnected, constrain);
//act
hubWinClient.SendToHub(new LogMessage());
//assert
var constrain2 = Is.True.After(2000, 100);
Assert.That(() => hubInvokeError == false, constrain2);
// act
hubWinClient.DisconnectFromHub();
// assert
var constrain1 = Is.True.After(2000, 100);
Assert.That(() => clientDisconnected, constrain1);
hubHost.Stop();
}
}
For some reason when i run my the test by resharper nunit runner, hub instance does created and my test fails. i think it's related to Resharper's and how it create test environment (i'e process creation and such stuff).
Any ideas.
Thanks.
I need to upload a shapefile and store all shapes in database. I spent two days to finding a good library but so far I do not find any complete solution.
I try to parse the file with 'Catfood.Shapefile' library, but it needs two other file beside of .shp file which I do not have.
I manage to implement a code in Java but now I need to re-implement it in c#.
please give me some advice!
Ok, Shp file is a well design file which you can see the description here. the shx and dbf only add some help to parse shp file. I add a class into Catfood.Shapefile which its name is ShpParser. The input of this class is only Shp file. it does not need to shx and dbf file to extract the shapes in the file.
if the meta data is null, two classes are throw exception, make sure you remove metadata exceptions from following method:
1- Shape constructor
2- ShapeFactory.ParseShape
and here the code of ShpParser
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.OleDb;
using System.IO;
using System.Text;
namespace Catfood.Shapefile
{
/// <summary>
///
/// </summary>
public class ShpParser : IDisposable, IEnumerator<Shape>, IEnumerable<Shape>
{
private bool _disposed;
private bool _opened;
private int _currentIndex;
private int _count;
private RectangleD _boundingBox;
private ShapeType _type;
private int _currentPosition;
private FileStream _mainStream;
private Header _mainHeader;
/// <summary>
///
/// </summary>
public ShpParser()
{
_currentIndex = -1;
_currentPosition = 0;
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
public ShpParser(string path)
: this()
{
Open(path);
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
private void Open(string path)
{
if (_disposed)
{
throw new ObjectDisposedException("ShpParser");
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
if (!File.Exists(path))
{
throw new FileNotFoundException("shp file not found", path);
}
_mainStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (_mainStream.Length < Header.HeaderLength)
{
throw new InvalidOperationException("Shapefile main file does not contain a valid header");
}
// read in and parse the headers
var headerBytes = new byte[Header.HeaderLength];
_mainStream.Read(headerBytes, 0, Header.HeaderLength);
_mainHeader = new Header(headerBytes);
_type = _mainHeader.ShapeType;
_boundingBox = new RectangleD(_mainHeader.XMin, _mainHeader.YMin, _mainHeader.XMax, _mainHeader.YMax);
_currentPosition = Header.HeaderLength;
_count = _mainHeader.FileLength * 2; // FileLength return the size of file in words (16 bytes)
_opened = true;
}
#region IDisposable Members
/// <summary>
/// Close the Shapefile. Equivalent to calling Dispose().
/// </summary>
public void Close()
{
Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool canDisposeManagedResources)
{
if (!_disposed)
{
if (canDisposeManagedResources)
{
if (_mainStream != null)
{
_mainStream.Close();
_mainStream = null;
}
}
_disposed = true;
_opened = false;
}
}
#endregion
public bool MoveNext()
{
if (_disposed) throw new ObjectDisposedException("ShpParser");
if (!_opened) throw new InvalidOperationException("ShpParser not open.");
if (_currentPosition < (_count - 1))
{
// try to read the next database record
return true;
}
else
{
// reached the last shape
return false;
}
}
public void Reset()
{
if (_disposed) throw new ObjectDisposedException("ShpParser");
if (!_opened) throw new InvalidOperationException("ShpParser not open.");
_currentIndex = -1;
}
public Shape Current
{
get
{
if (_disposed) throw new ObjectDisposedException("ShpParser");
if (!_opened) throw new InvalidOperationException("ShpParser not open.");
//get recordheader;
var recordheader = new byte[8];
_mainStream.Read(recordheader, 0, recordheader.Length);
int contentOffsetInWords = EndianBitConverter.ToInt32(recordheader, 0, ProvidedOrder.Big);
int contentLengthInWords = EndianBitConverter.ToInt32(recordheader, 4, ProvidedOrder.Big);
_mainStream.Seek(_currentPosition , SeekOrigin.Begin); // back to header of record
int bytesToRead = (contentLengthInWords * 2) + 8;
byte[] shapeData = new byte[bytesToRead];
_currentPosition += bytesToRead;
_mainStream.Read(shapeData, 0, bytesToRead);
return ShapeFactory.ParseShape(shapeData , null);
// return null;
}
}
object IEnumerator.Current
{
get
{
if (_disposed) throw new ObjectDisposedException("ShpParser");
if (!_opened) throw new InvalidOperationException("ShpParser not open.");
return this.Current;
}
}
public IEnumerator<Shape> GetEnumerator()
{
return (IEnumerator<Shape>)this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return (System.Collections.IEnumerator)this;
}
}
}
how you can use the ShpParser
using (var shapefile = new ShpParser("my.shp")
{
foreach (Shape shape in shapefile)
{
Console.WriteLine("ShapeType: {0}", shape.Type);
}
}
I have often run into situations where I need some sort of valve construct to control the flow of a reactive pipeline. Typically, in a network-based application I have had the requirement to open/close a request stream according to the connection state.
This valve subject should support opening/closing the stream, and output delivery in FIFO order. Input values should be buffered when the valve is closed.
A ConcurrentQueue or BlockingCollection are typically used in such scenarios, but that immediately introduces threading into the picture. I was looking for a purely reactive solution to this problem.
Here's an implementation mainly based on Buffer() and BehaviorSubject. The behavior subject tracks the open/close state of the valve. Openings of the valve start buffering windows, and closings of the valve close those windows. Output of the buffer operator is "re-injected" onto the input (so that even observers themselves can close the valve):
/// <summary>
/// Subject offering Open() and Close() methods, with built-in buffering.
/// Note that closing the valve in the observer is supported.
/// </summary>
/// <remarks>As is the case with other Rx subjects, this class is not thread-safe, in that
/// order of elements in the output is indeterministic in the case of concurrent operation
/// of Open()/Close()/OnNext()/OnError(). To guarantee strict order of delivery even in the
/// case of concurrent access, <see cref="ValveSubjectExtensions.Synchronize{T}(NEXThink.Finder.Utils.Rx.IValveSubject{T})"/> can be used.</remarks>
/// <typeparam name="T">Elements type</typeparam>
public class ValveSubject<T> : IValveSubject<T>
{
private enum Valve
{
Open,
Closed
}
private readonly Subject<T> input = new Subject<T>();
private readonly BehaviorSubject<Valve> valveSubject = new BehaviorSubject<Valve>(Valve.Open);
private readonly Subject<T> output = new Subject<T>();
public ValveSubject()
{
var valveOperations = valveSubject.DistinctUntilChanged();
input.Buffer(
bufferOpenings: valveOperations.Where(v => v == Valve.Closed),
bufferClosingSelector: _ => valveOperations.Where(v => v == Valve.Open))
.SelectMany(t => t).Subscribe(input);
input.Where(t => valveSubject.Value == Valve.Open).Subscribe(output);
}
public bool IsOpen
{
get { return valveSubject.Value == Valve.Open; }
}
public bool IsClosed
{
get { return valveSubject.Value == Valve.Closed; }
}
public void OnNext(T value)
{
input.OnNext(value);
}
public void OnError(Exception error)
{
input.OnError(error);
}
public void OnCompleted()
{
output.OnCompleted();
input.OnCompleted();
valveSubject.OnCompleted();
}
public IDisposable Subscribe(IObserver<T> observer)
{
return output.Subscribe(observer);
}
public void Open()
{
valveSubject.OnNext(Valve.Open);
}
public void Close()
{
valveSubject.OnNext(Valve.Closed);
}
}
public interface IValveSubject<T>:ISubject<T>
{
void Open();
void Close();
}
An additional method for flushing out the valve can be useful at times, e.g. to eliminate remaining requests when they are no longer relevant. Here is an implementation that builds upon the precedent, adapter-style:
/// <summary>
/// Subject with same semantics as <see cref="ValveSubject{T}"/>, but adding flushing out capability
/// which allows clearing the valve of any remaining elements before closing.
/// </summary>
/// <typeparam name="T">Elements type</typeparam>
public class FlushableValveSubject<T> : IFlushableValveSubject<T>
{
private readonly BehaviorSubject<ValveSubject<T>> valvesSubject = new BehaviorSubject<ValveSubject<T>>(new ValveSubject<T>());
private ValveSubject<T> CurrentValve
{
get { return valvesSubject.Value; }
}
public bool IsOpen
{
get { return CurrentValve.IsOpen; }
}
public bool IsClosed
{
get { return CurrentValve.IsClosed; }
}
public void OnNext(T value)
{
CurrentValve.OnNext(value);
}
public void OnError(Exception error)
{
CurrentValve.OnError(error);
}
public void OnCompleted()
{
CurrentValve.OnCompleted();
valvesSubject.OnCompleted();
}
public IDisposable Subscribe(IObserver<T> observer)
{
return valvesSubject.Switch().Subscribe(observer);
}
public void Open()
{
CurrentValve.Open();
}
public void Close()
{
CurrentValve.Close();
}
/// <summary>
/// Discards remaining elements in the valve and reset the valve into a closed state
/// </summary>
/// <returns>Replayable observable with any remaining elements</returns>
public IObservable<T> FlushAndClose()
{
var previousValve = CurrentValve;
valvesSubject.OnNext(CreateClosedValve());
var remainingElements = new ReplaySubject<T>();
previousValve.Subscribe(remainingElements);
previousValve.Open();
return remainingElements;
}
private static ValveSubject<T> CreateClosedValve()
{
var valve = new ValveSubject<T>();
valve.Close();
return valve;
}
}
public interface IFlushableValveSubject<T> : IValveSubject<T>
{
IObservable<T> FlushAndClose();
}
As mentioned in the comment, these subjects are not "thread-safe" in the sense that order of delivery is no longer guaranteed in the case of concurrent operation. In a similar fashion as what exists for the standard Rx Subject, Subject.Synchronize() (https://msdn.microsoft.com/en-us/library/hh211643%28v=vs.103%29.aspx) we can introduce some extensions which provide locking around the valve:
public static class ValveSubjectExtensions
{
public static IValveSubject<T> Synchronize<T>(this IValveSubject<T> valve)
{
return Synchronize(valve, new object());
}
public static IValveSubject<T> Synchronize<T>(this IValveSubject<T> valve, object gate)
{
return new SynchronizedValveAdapter<T>(valve, gate);
}
public static IFlushableValveSubject<T> Synchronize<T>(this IFlushableValveSubject<T> valve)
{
return Synchronize(valve, new object());
}
public static IFlushableValveSubject<T> Synchronize<T>(this IFlushableValveSubject<T> valve, object gate)
{
return new SynchronizedFlushableValveAdapter<T>(valve, gate);
}
}
internal class SynchronizedValveAdapter<T> : IValveSubject<T>
{
private readonly object gate;
private readonly IValveSubject<T> valve;
public SynchronizedValveAdapter(IValveSubject<T> valve, object gate)
{
this.valve = valve;
this.gate = gate;
}
public void OnNext(T value)
{
lock (gate)
{
valve.OnNext(value);
}
}
public void OnError(Exception error)
{
lock (gate)
{
valve.OnError(error);
}
}
public void OnCompleted()
{
lock (gate)
{
valve.OnCompleted();
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
return valve.Subscribe(observer);
}
public void Open()
{
lock (gate)
{
valve.Open();
}
}
public void Close()
{
lock (gate)
{
valve.Close();
}
}
}
internal class SynchronizedFlushableValveAdapter<T> : SynchronizedValveAdapter<T>, IFlushableValveSubject<T>
{
private readonly object gate;
private readonly IFlushableValveSubject<T> valve;
public SynchronizedFlushableValveAdapter(IFlushableValveSubject<T> valve, object gate)
: base(valve, gate)
{
this.valve = valve;
this.gate = gate;
}
public IObservable<T> FlushAndClose()
{
lock (gate)
{
return valve.FlushAndClose();
}
}
}
Here is my implementation with delay operator:
source.delay(new Func1<Integer, Observable<Boolean>>() {
#Override
public Observable<Boolean> call(Integer integer) {
return valve.filter(new Func1<Boolean, Boolean>() {
#Override
public Boolean call(Boolean aBoolean) {
return aBoolean;
}
});
}
})
.toBlocking()
.subscribe(new Action1<Integer>() {
#Override
public void call(Integer integer) {
System.out.println("out: " + integer);
}
});
The idea is to delay all source emissions until "valve opens". If valve is already opened, there will be no delay in emission of item.
Rx valve gist
I have a producer/consumer queue as following but I am getting ArgumentWException.
Following is the code:
public class ProducerConsumer<T> where T : class
{
#region Private Variables
private Thread _workerThread;
private readonly Queue<T> _workQueue;
private object _enqueueItemLocker = new object();
private object _processRecordLocker = new object();
private readonly Action<T> _workCallbackAction;
private AutoResetEvent _workerWaitSignal;
#endregion
#region Constructor
public ProducerConsumer(Action<T> action)
{
_workQueue = new Queue<T>();
_workCallbackAction = action;
}
#endregion
#region Private Methods
private void ProcessRecord()
{
while (true)
{
T workItemToBeProcessed = default(T);
bool hasSomeWorkItem = false;
lock (_processRecordLocker)
{
hasSomeWorkItem = _workQueue.Count > 0;
if (hasSomeWorkItem)
{
workItemToBeProcessed = _workQueue.Dequeue();
if (workItemToBeProcessed == null)
{
return;
}
}
}
if (hasSomeWorkItem)
{
if (_workCallbackAction != null)
{
_workCallbackAction(workItemToBeProcessed);
}
}
else
{
_workerWaitSignal.WaitOne();
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Enqueues work item in the queue.
/// </summary>
/// <param name="workItem">The work item.</param>
public void EnQueueWorkItem(T workItem)
{
lock (_enqueueItemLocker)
{
_workQueue.Enqueue(workItem);
if (_workerWaitSignal == null)
{
_workerWaitSignal = new AutoResetEvent(false);
}
_workerWaitSignal.Set();
}
}
/// <summary>
/// Stops the processer, releases wait handles.
/// </summary>
/// <param name="stopSignal">The stop signal.</param>
public void StopProcesser(AutoResetEvent stopSignal)
{
EnQueueWorkItem(null);
_workerThread.Join();
_workerWaitSignal.Close();
_workerWaitSignal = null;
if (stopSignal != null)
{
stopSignal.Set();
}
}
/// <summary>
/// Starts the processer, starts a new worker thread.
/// </summary>
public void StartProcesser()
{
if (_workerWaitSignal == null)
{
_workerWaitSignal = new AutoResetEvent(false);
}
_workerThread = new Thread(ProcessRecord) { IsBackground = true };
_workerThread.Start();
}
#endregion
}
Another class is:
public class Tester
{
private readonly ProducerConsumer<byte[]> _proConsumer;
public Tester()
{
_proConsumer = new ProducerConsumer<byte[]>(Display);
}
public void AddData(byte[] data)
{
try
{
_proConsumer.EnQueueWorkItem(recordData);
}
catch (NullReferenceException nre)
{
}
}
public void Start()
{
_proConsumer.StartProcesser();
}
private static object _recordLocker = new object();
private void Display(byte[] recordByteStream)
{
try
{
lock (_recordLocker)
{
Console.WriteLine("Done with data:" + BitConverter.ToInt32(recordByteStream, 0));
}
}
catch (Exception ex)
{
}
}
}
And my main function:
class Program
{
private static Tester _recorder;
static void Main(string[] args)
{
_recorder = new Tester();
_recorder.StartRecording();
for (int i = 0; i < 100000; i++)
{
_recorder.AddRecordData(BitConverter.GetBytes(i));
}
Console.Read();
}
}
Any idea why do I get the exception and what should I do to avoid that ?
Your class, in its current implementation, is not thread-safe. You're using two different objects for your Enqueue (lock (_enqueueItemLocker)) and Dequeue (lock (_processRecordLocker)) calls, which creates a race condition in your Queue<T>.
You need to lock the same object instance on both calls in order to safely use the queue.
If you're using .NET 4, I'd recommend either using ConcurrentQueue<T> or BlockingCollection<T> instead, as these would eliminate the need for the locks in your code, since they're thread-safe.