Hubproxy.On not firing after a long time - c#

I have a wpf application which use SignalR HubConnection. everything works as expected. But after a long time (for example 10 hours) hubproxy.on method not firing, server sending data to client correctly also hubproxy.Invoke works correctly, only hubproxy.On does not work. There is no connection error or disconnected state. It happens on Production environment
Here is my client code
public partial class MainWindow : Window
{
private IHubProxy hubProxy;
private HubConnection connection;
private string url = "http://localhost:8080/signalr";
public MainWindow()
{
InitializeConnection();
InitializeComponent();
}
private void InitializeConnection()
{
connection = new HubConnection(url);
hubProxy = connection.CreateHubProxy("SignalRHub");
connection.StateChanged += connection_StateChanged;
hubProxy.On<string>("OnMessage", OnMMessage);
connection.Start();
}
void connection_StateChanged(StateChange obj)
{
if (obj.NewState == ConnectionState.Disconnected)
{
InitializeConnection();
}
}
private void OnMMessage(string message)
{
//This not works sometimes
MessageBox.Show(message);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//this works always, is being logged on the server
hubProxy.Invoke("SendMessage2", new object[1] { "dfdfdf" });
}
}
this is server side
public class SignalRHub : Hub
{
public Logger logger = new Logger();
public void Subscribe()
{
SubscriberCache.Subscribers.Add(Context.ConnectionId);
}
public void UnSubscribe()
{
if (SubscriberCache.Subscribers.Contains(Context.ConnectionId))
SubscriberCache.Subscribers.Remove(Context.ConnectionId);
}
public override Task OnDisconnected(bool stopCalled)
{
UnSubscribe();
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
if (!SubscriberCache.Subscribers.Contains(Context.ConnectionId))
SubscriberCache.Subscribers.Add(Context.ConnectionId);
return base.OnReconnected();
}
public void SendMessage(string message)
{
//this works always
logger.Info("message sending to client");
Clients.All.OnMessage(message);
}
}

Related

websocket-sharp - OnMessage callback is not running in the main thread

I have a WPF (.NET Framework 4.6) application that uses websocket-sharp (version 3.0.0) to create a websocket server.
I have a WebsocketServer and using EventHandler to tranfer event to MainWindow.xaml.cs but it not working. The MainWindow.xaml.cs listened to a RaiseOnScanDevice event but not any event invoked here.
I think this issue is relative to different thread. I try using Dispatcher.Invoke but it still not working.
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
I found an issue (https://github.com/sta/websocket-sharp/issues/350) but the answers do not resolve my issue.
Please help me a solution for this issue.
WebsocketServer.cs file
public class WebsocketServer : WebSocketBehavior
{
private static readonly Lazy<WebsocketServer> lazyInstance = new Lazy<WebsocketServer>(() => new WebsocketServer());
public static WebsocketServer Instance
{
get
{
return lazyInstance.Value;
}
}
private const string TAG = "WebsocketServer";
private const string HOST_IP_ADDRESS = "127.0.0.2"; // localhost
private const int PORT = 38001;
public WebSocketServer socket;
private PacketHandler packetHandler = new PacketHandler();
public event EventHandler<EventArgs> RaiseOnScanDevice = new EventHandler<EventArgs>((a, e) => { });
public WebsocketServer()
{
Initialize();
}
public void Initialize()
{
socket = new WebSocketServer(IPAddress.Parse(HOST_IP_ADDRESS), PORT);
socket.AddWebSocketService<WebsocketServer>("/");
StartServer();
}
public void StartServer()
{
socket.Start();
}
public void StopServer()
{
socket.Stop();
}
protected override Task OnOpen()
{
return base.OnOpen();
}
protected override Task OnClose(CloseEventArgs e)
{
return base.OnClose(e);
}
protected override Task OnError(ErrorEventArgs e)
{
return base.OnError(e);
}
protected override Task OnMessage(MessageEventArgs e)
{
System.IO.StreamReader reader = new System.IO.StreamReader(e.Data);
string message = reader.ReadToEnd();
//Converting the event back to 'eventName' and 'JsonPayload'
PacketModel packet = packetHandler.OpenPacket(message);
HandleMessageFromClient(packet);
return base.OnMessage(e);
}
private void HandleMessageFromClient(PacketModel packet) {
var eventName = packet.EventName;
var data = packet.Data;
if (eventName == null || eventName.Equals(""))
{
return;
}
switch (eventName)
{
case SocketEvent.Hello:
Send("OK");
break;
case SocketEvent.ScanDevice:
ScanDevice();
break;
default:
break;
}
}
private void ScanDevice()
{
try
{
RaiseOnScanDevice(this, new EventArgs());
// or dispatch to Main Thread
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
}
MainWindow.xaml.cs file
public partial class MainWindow : Window
{
public WebsocketServer WebsocketConnection
{
get { return WebsocketServer.Instance; }
}
public MainWindow()
{
InitializeComponent();
WebsocketConnection.RaiseOnScanDevice += SocketConnection_RaiseOnScanDevice;
}
private void SocketConnection_RaiseOnScanDevice(object sender, EventArgs e)
{
Console.WriteLine("SocketConnection_RaiseOnScanDevice");
}
The queue of messages is a good idea but you may want to use a lock to guard access to it. Most likely it won't be an issue but if you don't, you leave yourself open to the possibility of an error if the coroutine is reading from the queue as the websocket is writing to it. For example you could do something like this:
var queueLock = new object();
var queue = new Queue<MyMessageType>();
// use this to read from the queue
MyMessageType GetNextMessage()
{
lock (queueLock) {
if (queue.Count > 0) return queue.Dequeue();
else return null;
}
}
// use this to write to the queue
void QueueMessage(MyMessageType msg)
{
lock(queueLock) {
queue.Enqueue(msg);
}
}

UoW and DbContext fail in an event

I have implemented this Windows Service which sends emails using SendAsync method, every 30 seconds in batches of 20. I'm using EF6 and SQL Server 2016. Here is some parts of the codes
EmailRepository.cs
public class EmailRepository : IEmailRepository
{
private BBEntities db = null;
public EmailRepository(BBEntities db)
{
this.db = db;
}
public IEnumerable<tb_Email> SelectAll(int batchAge, int batchSize)
{
DateTime tDate = DateTime.Now.AddMinutes(batchAge);
return db.tb_Email.Where(x => x.ReadyToSend.Equals(true) & x.DateSent.Equals(null) & x.DateCreated >= tDate).OrderBy(x => x.DateCreated).Take(batchSize);
}
public tb_Email SelectByID(Guid id)
{
return db.tb_Email.Find(id);
}
public void Update(tb_Email obj)
{
db.Entry(obj).State = EntityState.Modified;
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
db.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
{
private readonly BBEntities ctx = new BBEntities();
private IEmailRepository emailRepository;
public IEmailRepository EmailRepository
{
get
{
if (this.emailRepository == null)
{
this.emailRepository = new EmailRepository(ctx);
}
return emailRepository;
}
}
public void Dispose()
{
this.ctx.Dispose();
}
public void Commit()
{
this.ctx.SaveChanges();
}
}
EmailService.cs
public class EmailService : IEmailService
{
private IUnitOfWork unitOfWork;
public EmailService()
{
unitOfWork = new UnitOfWork();
}
public List<tb_Email> SelectAll(int batchAge, int batchSize)
{
return unitOfWork.EmailRepository.SelectAll(batchAge, batchSize).ToList();
}
public tb_Email SelectByID(Guid id)
{
return unitOfWork.EmailRepository.SelectByID(id);
}
public void Update(tb_Email obj)
{
using (unitOfWork = new UnitOfWork())
{
unitOfWork.EmailRepository.Update(obj);
unitOfWork.Commit();
}
}
}
SMTPService.cs
public class SMTPService : ISMTPService
{
SmtpClient client;
MailMessage newMessage;
EmailService emailService;
IEventLoggerService MailCheckerLog;
public SMTPService()
{
emailService = new EmailService();
MailCheckerLog = new EventLoggerService();
}
public void SendEmail(tb_Email email)
{
try
{// rest of the code .....
newMessage = new MailMessage();
newMessage.Headers.Add("X-Email_Id", email.Id.ToString());
client.SendCompleted += (sender, e) => SendCompletedCallback(sender, e);
tb_Email userState = email;
//
// if I put the update database logic here, it works fine
//
client.SendAsync(newMessage, userState);
}
catch (Exception e)
{
MailCheckerLog.log("Error in SendComplete event handler - Exception: " + e.Message.ToString() + " -- InnerException: " + e.InnerException.Message, EventLogEntryType.Error);
client.Dispose();
newMessage.Dispose();
throw;
}
}
void SendCompletedCallback(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
tb_Email email = (tb_Email)e.UserState;
Console.WriteLine("----------------------------------" + emailID.Id);
email.ReadyToSend = false;
emailService.Update(email);
client.Dispose();
newMessage.Dispose();
}
}
The problem:
So to send and process emails I run SendEmail method in a simple loop with a list of tb_Email objects, once each email is sent I have to update the database.
To do that, I use
email.ReadyToSend = false;
emailService.Update(email);
in my SendCompleted event, as I'm using SendAsync the system goes ahead and process many emails however the SendCompleted event might fire a bit later for each email.
To make sure it is using a unique and single dbContext I am using a using statement on my UoW instance for the update method. This works fine if I put my update logic in SendEmail method directly (which doesn't make any sense as I need to know if the email was sent successfully or not), however If I put it in the event after a few successful updates, it just throw
System.Data.Entity.Core.EntityException: 'The underlying provider failed on Open.'
I don't understand how is it possible when I'm actually creating a new context for each operation.
Sorry I have to answer it myself, the problem was that the UoW variable was still being used by other threads, so the solution is to declare a new variable for the using statement inside the update method, like below
public class EmailService : IEmailService
{
private IUnitOfWork unitOfWork;
public EmailService()
{
unitOfWork = new UnitOfWork();
}
public List<tb_Email> SelectAll(int batchAge, int batchSize)
{
return unitOfWork.EmailRepository.SelectAll(batchAge, batchSize).ToList();
}
public tb_Email SelectByID(Guid id)
{
return unitOfWork.EmailRepository.SelectByID(id);
}
public void Update(tb_Email obj)
{
IUnitOfWork unitOfWorkUpdate;
using (unitOfWorkUpdate = new UnitOfWork())
{
unitOfWorkUpdate.EmailRepository.Update(obj);
unitOfWorkUpdate.Commit();
}
}
}

Stop Timer in another class

I have a program that sends and receives messages over serial.
I have this Sender class:
public class Sender
{
private System.Timers.Timer _responseTimer;
public Sender(SerialPort sp)
{
_serialPort = sp;
_responseTimer = new System.Timers.Timer(2000);
}
public void Attach(ISenderObserver iso)
{
_responseTimer.Elapsed += new ElapsedEventHandler(iso.ResponseTooSlowEvent);
}
public void SendCommand(String command)
{
//start response timeout timer
_responseTimer.AutoReset = false;
_responseTimer.Enabled = true;
_serialPort.Write(command);
}
}
And then I have this receiving class:
public class Receiver : ISenderObserver
{
private static bool _continue;
private static SerialPort _serialPort;
private Thread _receiveThread;
public Receiver(SerialPort sp)
{
_serialPort = sp;
_continue = true;
_serialPort.Open();
//Start the receiving thread
_receiveThread = new Thread(Receive);
_receiveThread.Start();
}
public void Receive()
{
while (_continue)
{
String receivedMessage = _serialPort.ReadLine();
//parse received message
}
}
public void ResponseTooSlowEvent(object source, System.Timers.ElapsedEventArgs e)
{
Console.Write("\nToo Slow!");
}
}
And this interface:
public interface ISenderObserver
{
void ResponseTooSlowEvent(object source, ElapsedEventArgs e);
}
They are called like this in their main controller:
sender = new Sender(_serialPort);
receiver = new Receiver(_serialPort);
sender.Attach(receiver);
The reason for the timer is that I want the program to abort its waiting for a certain message if it takes too long, thereby avoiding a deadlock if it gets disconnected.
I thereby want to stop the timer within the Receiver-class as soon as line:
String receivedMessage = _serialPort.ReadLine();
is finished.
How can I do this without having dependencies all over the place?

Storing the connection id of a specific client in SignalR hub

Below you can see a simplified version of my SignalR self hosted hub on a windows service:
public static class SubscriptionHandler
{
public static int PriceFeedMembersCount = 0;
}
public class PriceHub : Hub
{
public Task SubscribeToPriceFeed()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PriceHub>();
if (SubscriptionHandler.PriceFeedMembersCount == 0)
{
context.Clients.All.updatePriceSubscriptionStatus(true);
}
SubscriptionHandler.PriceFeedMembersCount++;
return context.Groups.Add(Context.ConnectionId, "PriceFeed");
}
public Task UnsubscribeFromPriceFeed()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PriceHub>();
SubscriptionHandler.PriceFeedMembersCount--;
if (SubscriptionHandler.PriceFeedMembersCount == 0)
{
context.Clients.All.updatePriceSubscriptionStatus(false);
}
return context.Groups.Remove(Context.ConnectionId, "PriceFeed");
}
public void NotifySubscribers(Price price)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PriceHub>();
context.Clients.Group("PriceFeed").updatePrice(price);
}
}
And I have two types of clients for that hub: One of them is web applications and the other one is windows services. Here you can see a demo implementation for my windows service as a signalr client:
public partial class WinSer45 : ServiceBase
{
private HubConnection hubConnection;
private IHubProxy priceProxy;
private Timer timer = new Timer();
private bool hasSubscribers = false;
public WinSer45()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
timer.Interval = 1000; // saniyede bir
timer.Elapsed += timer_Elapsed;
timer.Enabled = true;
hubConnection = new HubConnection("http://localhost:8080/signalr", useDefaultUrl: false);
priceProxy = hubConnection.CreateHubProxy("PriceHub");
hubConnection.Start().Wait();
priceProxy.On<bool>("UpdatePriceSubscriptionStatus", hasSubscribers =>
{
this.hasSubscribers = hasSubscribers;
});
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (hasSubscribers)
{
TestPrice testPrice = new TestPrice() { Id = 1, Buy = 1.2345, Sell = 9.8765, Symbol = "EURUSD" };
priceProxy.Invoke("NotifySubscribers", testPrice).Wait();
}
}
protected override void OnStop()
{
}
}
As you see I use the hasSubscribers flag to minimize the messages between hub and clients. And hasSubscribers flag is changed by SubscribeToPriceFeed and UnsubscribeFromPriceFeed methods.
If you look carefully you see the line below in SubscribeToPriceFeed:
context.Clients.All.updatePriceSubscriptionStatus(true);
I don't want to send the message to all clients but my client windows service. How can I store the connection Id of a specific client in my hub? If I can do that, I know I can send message to a specific connectionId as in the line below:
context.Clients.Client(connectionId).updatePriceSubscriptionStatus(true);
Thanks in advance,
pass source during connection
like this
hubConnection = new HubConnection("http://localhost:8080/signalr","source=windows",useDefaultUrl: false);
HUB
public override Task OnConnected()
{
var source= Context.QueryString['source'];
return base.OnConnected();
}
create a class which will hold the user with source
public class user {
public string ConnectionID {set;get;}
public string Source {set;get;}
}
declare a list in the hub
List<user> userList=new List<user>();
Then push the user during OnConnected
public override Task OnConnected()
{
var us=new user();
us.Source = Context.QueryString['source'];
us.ConnectionID=Context.ConnectionId;
userList.Add(us);
return base.OnConnected();
}
and during broadcast just filter it by source
var windowsUser=userList.Where(o=>o.Source == "windows").ToList(); // you'll get the windows user list

"Delegate to an instance method cannot have null 'this' " while forwarding an event

Im trying to forward an event OnClientMessage from my class Client over the class Server to outside my libary.
Client.cs
public class Client
{
private TcpClient tcpClient;
private StreamWriter writer;
private Boolean alive = true;
private int id;
public delegate void OnClientMessageHandler(Client sender, String message);
public delegate void OnClientDisconnectHandler(Client sender);
public event OnClientMessageHandler OnClientMessage;
public event OnClientDisconnectHandler OnClientDisconnect;
public Client(TcpClient tcpClient, int id)
{
this.tcpClient = tcpClient;
this.id = id;
writer = new StreamWriter(tcpClient.GetStream());
new Thread(() =>
{
Listen(new StreamReader(tcpClient.GetStream()));
}).Start();
}
void Listen(StreamReader reader)
{
while (tcpClient.GetStream().DataAvailable && alive)
{
OnClientMessage(this, reader.ReadLine());
Thread.Sleep(150);
}
}
public void Write(String message)
{
writer.WriteLine(message);
writer.Flush();
}
public int GetID()
{
return id;
}
public void Close()
{
alive = false;
writer.Close();
tcpClient.Close();
OnClientDisconnect(this);
}
}
Server.cs
public class Server
{
private IPAddress serverIP;
private short serverPort;
private TcpListener serverListener;
private int serverClientCount;
public List<Client> serverClients = new List<Client>();
private Boolean running;
public delegate void OnClientMessageHandler(Client sender, String message);
public delegate void OnClientDisconnectHandler(Client sender);
public event OnClientMessageHandler OnClientMessage;
public event OnClientDisconnectHandler OnClientDisconnect;
public Server(IPAddress ip, short port, Boolean autoStart = true)
{
this.serverIP = ip;
this.serverPort = port;
if(autoStart)
OpenServer();
}
public void OpenServer()
{
serverListener = new TcpListener(serverIP, serverPort);
serverListener.Start();
running = true;
while (running)
{
if (serverListener.Pending())
{
TcpClient tcpClient = serverListener.AcceptTcpClient();
new Thread(() =>
{
Client client;
client = new Client(tcpClient, serverClientCount);
client.OnClientMessage += new Client.OnClientMessageHandler(OnClientMessage);
client.OnClientDisconnect += new Client.OnClientDisconnectHandler(OnClientDisconnect);
serverClients.Add(client);
serverClientCount++;
}).Start();
}
else
{
Thread.Sleep(150);
}
}
}
public void WriteToClient(Client client, String message)
{
client.Write(message);
}
public void WriteToAll(String message)
{
serverClients.ForEach(client => client.Write(message));
}
public void Shutdown()
{
running = false;
serverClients.ForEach(client => client.Close());
serverListener.Stop();
}
}
Now when the event is firing the application crashes with Delegate to an instance method cannot have null 'this'.
Are I'm doing something wrong or isn't this the right way to forward an event?
This is pretty unique, never once seen anybody do this. It is a regression in the .NET Framework, 3.5 gives you a much better exception. Basic problem is that you made the event subscribe itself. A simple version to repro that crash:
using System;
class Program {
public static event Action Kaboom;
static void Main(string[] args) {
Kaboom += new Action(Kaboom); // 3.5 raises an exception here
var handler = Kaboom;
if (handler != null) handler(); // kaboom
}
}
You are not actually "forwarding" the event. Untangle your names and code. Add, say, a Fire() method.

Categories

Resources