I wet through this except that I added it to a windows service like this
public partial class TriggerHostProcesses : ServiceBase
{
private const string MESSAGE_QUEUE = #".\Private$\Sample Queue";
private MessageQueue _queue;
public TriggerHostProcesses()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
SendMessage("Hope This Works");
}
protected override void OnStop()
{
}
internal void start()
{
OnStart(null);
}
private void SendMessage(string message)
{
_queue = new MessageQueue(MESSAGE_QUEUE);
Message msg = new Message();
msg.Body = message;
msg.Label = "Testing " + DateTime.Now.ToString();
_queue.Send(msg,new MessageQueueTransaction());
}
}
and to get the message
partial class HostListener : ServiceBase
{
private const string MESSAGE_QUEUE = #".\Private$\Sample Queue";
private MessageQueue _queue;
public HostListener()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
var myTransaction = new MessageQueueTransaction();
var queue = new MessageQueue(MESSAGE_QUEUE);
var message = queue.Receive(new TimeSpan(0, 0, 20),myTransaction);
message.Formatter = new XmlMessageFormatter(
new String[] { "System.String,mscorlib" });
Console.WriteLine(message.Body.ToString());
}
catch(Exception ex)
{
Console.WriteLine("No Message");
}
}
protected override void OnStop()
{
// TODO: Add code here to perform any tear-down necessary to stop your service.
}
internal void start()
{
OnStart(null);
}
}
in my main I added this
var ServiceToRun1 = new TriggerHostProcesses();
var ServiceToRun2 = new HostListener();
if (Environment.UserInteractive)
{
// This used to run the service as a console (development phase only)
ServiceToRun1.start();
ServiceToRun2.start();
Console.WriteLine("Press Enter to terminate ...");
Console.ReadLine();
ServiceToRun1.Stop();
ServiceToRun2.Stop();
}
else
{
ServiceBase.Run(ServiceToRun1);
}
I get the exception Timeout for the requested operation has expired.
Can someone please check if they can see what the problem is?
I don't believe you are using transactions correctly. For example, when sending a message you use:
_queue.Send(msg,new MessageQueueTransaction());
However, this does not begin or commit a transaction. Looking in MSDN the example uses the following code (edited by me):
var myTransaction = new MessageQueueTransaction();
myTransaction.Begin();
myQueue.Send("hello world", myTransaction);
myTransaction.Commit();
I don't believe your message is getting sent, and so your Receive times out.
Similarly your receive logic doesn't seem to correctly use transactions:
myTransaction.Begin();
var myMessage = myQueue.Receive(myTransaction);
var body myOrder = (string)myMessage.Body;
myTransaction.Commit();
You should Rollback in the event of an exception processing your messages so they can be placed back on the queue.
Here is my final product. I'm using this in a windows service. 20 s at a time to see if I have a message then do my processes.
public class MSMQueue:IQueue
{
public MSMQueue(string queueName)
{
Message_Queue = queueName;
}
public string Message_Queue { get; private set; }
public string Pop()
{
MessageQueue queue = new MessageQueue(Message_Queue);
if (queue.Transactional)
return popTransactionalQueue(queue, new TimeSpan(0, 0, 1));
else
return popNormalQueue(queue, new TimeSpan(0, 0, 1));
}
public string Pop(TimeSpan timeSpan)
{
MessageQueue myQueue = new MessageQueue(Message_Queue);
if (myQueue.Transactional)
return popTransactionalQueue(myQueue, timeSpan);
else
return popNormalQueue(myQueue, timeSpan);
}
public void Add(string message)
{
// Connect to a queue on the local computer.
MessageQueue myQueue = new MessageQueue(Message_Queue);
// Send a message to the queue.
if (myQueue.Transactional)
{
var myTransaction = new MessageQueueTransaction();
myTransaction.Begin();
myQueue.Send(message, myTransaction);
myTransaction.Commit();
}
else
myQueue.Send(message);
}
#region private methods
private string popNormalQueue(MessageQueue queue, TimeSpan timeOut)
{
var message = queue.Receive(timeOut);
message.Formatter = new XmlMessageFormatter(
new String[] { "System.String,mscorlib" });
return message.Body.ToString();
}
private string popTransactionalQueue(MessageQueue queue, TimeSpan timeOut)
{
// Set the formatter.
queue.Formatter = new XmlMessageFormatter(new Type[]
{typeof(String)});
// Create a transaction.
MessageQueueTransaction myTransaction = new
MessageQueueTransaction();
String message=string.Empty;
try
{
myTransaction.Begin();
Message myMessage = queue.Receive(timeOut, myTransaction);
message = (String)myMessage.Body;
myTransaction.Commit();
}
catch (MessageQueueException e)
{
myTransaction.Abort();
throw e;
}
return message;
}
#endregion
}
Related
I am first time working with both windows service and MSMQ. I am trying to read messages from queue. When I start my windows service, i am receiving only first message and next message not able to read, service is still running. if I restart the service It is reading first message from the queue. Please let me know how to fix this issue.
This is my code on start of my service:
protected override void OnStart(string[] args)
{
MessageQueue msMq = null;
JobModel j = new JobModel();
msMq = new MessageQueue(queueRequestName);
try
{
if (msMq != null)
{
msMq.Formatter = new XmlMessageFormatter(new Type[] { typeof(JobModel) });
var message = (JobModel)msMq.BeginReceive();
}
}
catch (MessageQueueException ee)
{
Console.Write(ee.ToString());
}
catch (Exception eee)
{
Console.Write(eee.ToString());
}
finally
{
msMq.Close();
}
}
Just a guess, but I think you shouldn't close the queue:
//keep your queue object in the service scope
//you might need more
MessageQueue msMq = null;
protected override void OnStart(string[] args)
{
JobModel j = new JobModel();
msMq = new MessageQueue(queueRequestName);
try
{
if (msMq != null)
{
msMq.Formatter = new XmlMessageFormatter(new Type[] { typeof(JobModel) });
var message = (JobModel)msMq.BeginReceive();
}
}
catch (MessageQueueException ee)
{
Console.Write(ee.ToString());
}
catch (Exception eee)
{
Console.Write(eee.ToString());
}
}
//close when you stop
protected override OnStop() //signature might be differnt
{
msMq.Close();
}
Have you tried adding an event handler and attaching it to the ReceiveCompleted event, as shown in MessageQueue.BeginReceive?
protected override void OnStart(string[] args)
{
MessageQueue msMq = null;
JobModel j = new JobModel();
msMq = new MessageQueue(queueRequestName);
msMq.ReceiveCompleted += ReceiveCompletedEventHandler(MyMsMqEventHandler)
try
{
if (msMq != null)
{
msMq.Formatter = new XmlMessageFormatter(new Type[] { typeof(JobModel) });
var message = (JobModel)msMq.BeginReceive();
}
}
catch (MessageQueueException ee)
{
Console.Write(ee.ToString());
}
catch (Exception eee)
{
Console.Write(eee.ToString());
}
}
public static void MyMsMqEventHandler(object src, ReceiveCompletedEventHandler handler)
{
var msMq = (MessageQueue)src;
var msg = msMq.EndReceive(handler.AsyncResult);
Console.WriteLine((string)msMq.Body);
msMq.BeginReceive();
}
https://github.com/mdevilliers/SignalR.RabbitMq/issues/43
I'm having real difficulty getting this to work, can someone please glance at this and see if something is wrong in the setup?
Here's my test:
I spin up two self hosted servers, configured to the same RabbitMqScaleoutConfiguration
[Test]
public void BasicBackplaneTest()
{
SubsciberTestServerNode nodeA = null;
SubsciberTestServerNode nodeB = null;
string messageA = null;
string messageB = null;
try
{
Log("Given I have a WorkCenter Dispatch Publisher");
var publisher = new WcDispatchPublisher(ConnectionString);
Log("And I have multiple server nodes subscribed to the backplane");
nodeA = new SubsciberTestServerNode("nodeA").Start().Result;
nodeB = new SubsciberTestServerNode("nodeB").Start().Result;
Log("And I wait 5 seconds");
Thread.Sleep(5000);
Log("When I publish a message: {0}", TestPayload);
publisher.Publish(TestPayload);
Log("And I wait 60 seconds");
Thread.Sleep(TimeSpan.FromSeconds(60));
messageA = nodeA.Message;
messageB = nodeB.Message;
}
catch (AggregateException exception)
{
Log("Exception Occurred: {0}", exception.Flatten().Message);
Exception = exception;
}
catch (Exception exception)
{
Log("Exception Occurred: {0}", exception.Message);
Exception = exception;
}
finally
{
nodeA?.Dispose();
nodeB?.Dispose();
Log("Then no exceptions should have been thrown.");
Exception.Should().BeNull();
Log("Then the message should have been added to the Message Queue");
messageA.Should().NotBeNullOrWhiteSpace();
messageB.Should().NotBeNullOrWhiteSpace();
}
Server:
internal class SubsciberTestServerNode : IDisposable
{
private readonly string _nodeName;
private readonly string _url;
private WcDispatchSubscriber _subscriber;
private IDisposable _webApp;
public SubsciberTestServerNode(string nodeName)
{
_nodeName = nodeName;
_url = $"http://localhost:9999/{nodeName}";
MessageList = new List<string>();
}
public string Message { get; set; }
public List<string> MessageList { get; set; }
public void Dispose()
{
if (_webApp != null)
{
_webApp.Dispose();
_webApp = null;
_subscriber.Dispose();
_subscriber = null;
}
}
public async Task<SubsciberTestServerNode> Start()
{
_webApp = WebApp.Start(_url, app =>
{
new Startup(_nodeName).Configuration(app);
Thread.Sleep(TimeSpan.FromSeconds(5));
//Place this code into your Application_Start() method.
var factory = new ConnectionFactory
{
UserName = "guest",
Password = "guest",
HostName = "localhost"
};
var exchangeName = "WC_LeadDispatch_Exchange";
var configuration = new RabbitMqScaleoutConfiguration(factory, exchangeName);
GlobalHost.DependencyResolver.UseRabbitMq(configuration);
GlobalHost.Configuration.TransportConnectTimeout = TimeSpan.FromSeconds(10);
Thread.Sleep(TimeSpan.FromSeconds(5));
});
_subscriber = new WcDispatchSubscriber();
await _subscriber.Subscribe(_url, msg =>
{
string message = $"Message received at Node: {_nodeName}. Message: {msg}.";
Console.WriteLine(message);
Message = message;
MessageList.Add(message);
});
return this;
}
}
Subscriber:
public class WcDispatchSubscriber : IDisposable
{
private const string HubName = "DispatchUpdateHub";
private const string MessageEventName = "addMessage";
private readonly int _connectionLimitInt;
private IDisposable _hubProxySubscription;
public WcDispatchSubscriber()
{
string connectionLimit = ConfigurationManager.AppSettings.Get("SignalRConnectionLimit");
int.TryParse(connectionLimit, out _connectionLimitInt);
_connectionLimitInt = _connectionLimitInt == 0 ? 100 : _connectionLimitInt;
}
public void Dispose()
{
_hubProxySubscription.Dispose();
}
public async Task Subscribe(string hubConnectionString, Action<string> messageReceived)
{
var hubConnection = new HubConnection(hubConnectionString);
IHubProxy dispatchHubProxy = hubConnection.CreateHubProxy(HubName);
_hubProxySubscription = dispatchHubProxy.On(MessageEventName, messageReceived);
ServicePointManager.DefaultConnectionLimit = _connectionLimitInt;
await hubConnection.Start();
}
}
Publisher:
public class WcDispatchPublisher
{
private const string ExchangeName = "WC_LeadDispatch_Exchange";
private readonly IHubContext _hubContext;
public WcDispatchPublisher(string connectionString)
{
//actual string will look like this. we may need to overload the other constructors in the Rabbit/SigR.
//_rabbitConnectionString =
// "host=cprmqsrvt02vn01:5672;publisherConfirms=true;username=unittest;password=Un1t735t;virtualhost=UnitTest-NotificationService";
var configuration = new RabbitMqScaleoutConfiguration(connectionString, ExchangeName);
GlobalHost.DependencyResolver.UseRabbitMq(configuration);
_hubContext = GlobalHost.ConnectionManager.GetHubContext<DispatchUpdateHub>();
}
/// <summary>
/// </summary>
/// <param name="payload"></param>
public void Publish(string payload)
{
Task.Factory.StartNew(() =>
{
_hubContext.Clients.All.addMessage(payload);
}).Wait();
}
}
this will work every say 12th run or so, normally here's what I get:
Given I have a WorkCenter Dispatch Publisher
And I have multiple server nodes subscribed to the backplane
And I wait 5 seconds
When I publish a message: test-payload
And I wait 60 seconds
Message received at Node: nodeA. Message: test-payload.
Message received at Node: nodeB. Message: test-payload.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":" Leads Dispatch","DispatchDateTime":"2016-06- 16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":" Leads Dispatch","DispatchDateTime":"2016-06- 16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":" Leads Dispatch","DispatchDateTime":"2016-06- 16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":" Leads Dispatch","DispatchDateTime":"2016-06- 16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":"Leads Dispatch","DispatchDateTime":"2016-06-16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
Message received at Node: nodeB. Message: {"LeadId":37252,"DispatchId":153595,"NotificationTypeId":1,"NotificationTitle":"Leads Dispatch","DispatchDateTime":"2016-06-16T17:16:26.187","AlertMessage":"Lead number 37252 dispatched 6/16/2016 at 7:16 AM CST","DispatchedToFranchise":5576,"FranchiseOpsId":130}.
... CONTINUES LIKE THIS FOREVER
Off the top of my head, I would say try:
nodeA = new SubsciberTestServerNode("nodeA").Start().Wait();
nodeB = new SubsciberTestServerNode("nodeB").Start().Wait();
or
nodeA = await new SubsciberTestServerNode("nodeA").Start();
nodeB = await new SubsciberTestServerNode("nodeB").Start();
Result is a thread blocking call, so its likely blocking nodeA from firing.
I am trying to connect from my C# code to IBM MQ.
Callback works for one listener (listening to a queue). i.e. when a message comes to the queue, callback method is correctly invoked.
I created another queue#2 on the same QueueManager / Channel. Registered a new listener on this queue with a different callback method. No errors are observed during server startup.
When a message arrives at this queue#2, callback associated with the first queue is invoked and not the one that I have registered for this queue. I tried by creating a different session, and also a different connection, but still the behaviour is the same.
Does anybody have any ideas?
class Program
{
static void Main(string[] args)
{
string env = "DEV"
string queueName1= "Queue1"
string queueName2 = "Queue2"
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
}
public class MyListener
{
public delegate void Handler (IMessage msg)
public static Handler _handler
private string env = "";
private string queue = "";
public MyListner(Handler _handler, string environment, string queueName)
{
_handler = _handler;
this.env = environment;
this.queue = queueName
}
public void RegisterListener()
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "10.87.188.156(7111)");
cf.SetIntProperty(XMSC.WMQ_PORT, 7111);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "QMEIGS1.CRM.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT_UNMANAGED);
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QMEIGS1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(OnMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
private void onMessage(IMessage m)
{
try {
_handler(m)
}
catch (Exception e )
{
}
}
}
//callback 1
public class CallbackHandler1
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage1
}
//callback 2
public class CallbackHandler2
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage2
}
I am not a hardcore c# programmer but I think the problem is because of the static keyword in public static Handler _handler;. The keyword static makes only one instance of _handler available across all instances of a MyListener class. Although you are creating two consumers and assigning two listeners, the second call to assign the message listener is overwriting the first listener. Hence you are receiving messages from only one queue.
I modified your code little bit as it was not compiling and got it working after removing the static keyword. I used MQ v8 for compiling the program. But the version should not be a problem for you.
namespace multilistener
{
class Program
{
static void Main(string[] args)
{
string env = "DEV";
string queueName1= "SUB.Q";
string queueName2 = "SUB.Q1";
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
System.Threading.Thread.Sleep(30000);
Console.WriteLine("Program ends");
}
}
public class MyListener
{
public delegate void Handler (IMessage msg);
public Handler _handler; // Remove 'static' keyword
private string env = "";
private string queue = "";
public MyListener(Handler _Inhandler, string environment, string queueName)
{
_handler = _Inhandler;
this.env = environment;
this.queue = queueName;
}
public void RegisterListener()
{
try
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "localhost");
cf.SetIntProperty(XMSC.WMQ_PORT, 1414);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "MY.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT);
cf.SetStringProperty(XMSC.USERID, "userid");
cf.SetStringProperty(XMSC.PASSWORD, "password");
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QM1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(onMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private void onMessage(IMessage m)
{
try {
_handler(m);
}
catch (Exception e )
{
Console.Write(e);
}
}
}
//callback 1
public class CallbackHandler1
{
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage1
Console.WriteLine("First consumer");
}
}
//callback 2
public class CallbackHandler2
{
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage2
Console.WriteLine("Second consumer");
}
}
}
I saw a lot of examples about GeginGetContext but i have filling that all of them are waste time. Maybe i am wrong. Lets find out. Lets take for instance really good example from the Multi-threading with .Net HttpListener topic:
public class HttpListenerCallbackState
{
private readonly HttpListener _listener;
private readonly AutoResetEvent _listenForNextRequest;
public HttpListenerCallbackState(HttpListener listener)
{
if (listener == null) throw new ArgumentNullException("listener");
_listener = listener;
_listenForNextRequest = new AutoResetEvent(false);
}
public HttpListener Listener { get { return _listener; } }
public AutoResetEvent ListenForNextRequest { get { return _listenForNextRequest; } }
}
public class HttpRequestHandler
{
private int requestCounter = 0;
private ManualResetEvent stopEvent = new ManualResetEvent(false);
public void ListenAsynchronously(IEnumerable<string> prefixes)
{
HttpListener listener = new HttpListener();
foreach (string s in prefixes)
{
listener.Prefixes.Add(s);
}
listener.Start();
HttpListenerCallbackState state = new HttpListenerCallbackState(listener);
ThreadPool.QueueUserWorkItem(Listen, state);
}
public void StopListening()
{
stopEvent.Set();
}
private void Listen(object state)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)state;
while (callbackState.Listener.IsListening)
{
callbackState.Listener.BeginGetContext(new AsyncCallback(ListenerCallback), callbackState);
int n = WaitHandle.WaitAny(new WaitHandle[] { callbackState.ListenForNextRequest, stopEvent});
if (n == 1)
{
// stopEvent was signalled
callbackState.Listener.Stop();
break;
}
}
}
private void ListenerCallback(IAsyncResult ar)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)ar.AsyncState;
HttpListenerContext context = null;
int requestNumber = Interlocked.Increment(ref requestCounter);
try
{
context = callbackState.Listener.EndGetContext(ar);
}
catch (Exception ex)
{
return;
}
finally
{
callbackState.ListenForNextRequest.Set();
}
if (context == null) return;
HttpListenerRequest request = context.Request;
if (request.HasEntityBody)
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(request.InputStream, request.ContentEncoding))
{
string requestData = sr.ReadToEnd();
//Stuff I do with the request happens here
}
}
try
{
using (HttpListenerResponse response = context.Response)
{
//response stuff happens here
string responseString = "Ok";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.LongLength;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.Close();
}
}
catch (Exception e)
{
}
}
}
Here we can see main part for this topic:
while (callbackState.Listener.IsListening)
{
callbackState.Listener.BeginGetContext(new AsyncCallback(ListenerCallback), callbackState);
int n = WaitHandle.WaitAny(new WaitHandle[] { callbackState.ListenForNextRequest, stopEvent});
...
}
I can saw this patterns in +- all examples.
We are starting Getting Contex (Getting request = getting network stream with request) after that we are blocking current thread with Wait/WaitAny method, so thread witch is getting request is doing nothing until it will got full request after that it will getting new request. For example we are having WCF
request with large object(which are deserialized in this request and are serialized in the same way on other side) so we will wait and WASTE TIME until we complete getting full steam with this request.
I see that really it is Sync not Async way. Instead of this we can starting getting second request
right after the starting getting first and not call Wait, not blocking thread. I think we will have much better performance in such way. What do you think? Why do all examples contains Wait?
I'm trying to create a quite simple notifications system (don't want to use SignalIR or something else). I have the following testing code:
Client side:
var source = new EventSource('/notifications.axd');
source.onopen = function () {
Console.log("Connection open");
};
source.onerror = function () {
Console.log("Connection error");
};
source.onmessage = function (event) {
Console.log("Message: " + event.data);
};
Server side:
public class NotificationMessage {
public NotificationMessage() {
Id = Guid.NewGuid().ToString();
}
public string Id { get; private set; }
}
public class NotificationsHandler : HttpTaskAsyncHandler {
private const string CONTENT_TYPE = "text/event-stream";
private sealed class NotificationItem {
public ConcurrentQueue<NotificationMessage> Messages;
public CancellationTokenSource CancellationTokenSource;
}
private static readonly ConcurrentDictionary<string, NotificationItem> _tasks =
new ConcurrentDictionary<string, NotificationItem>();
public static void Notify(string hostId, string userId, NotificationMessage message) {
NotificationItem item;
if (!_tasks.TryGetValue(string.Format("{0}|{1}", hostId, userId), out item)) {
return;
}
var tokenSource = item.CancellationTokenSource;
item.Messages.Enqueue(message);
item.CancellationTokenSource = new CancellationTokenSource();
tokenSource.Cancel();
}
public override async Task ProcessRequestAsync(HttpContext context) {
HttpRequest request = context.Request;
NotificationItem item = _tasks.GetOrAdd(
string.Format("{0}|{1}", request.Url.Host, CsSession.Data.CurrentUser.Id),
k => new NotificationItem {
Messages = new ConcurrentQueue<NotificationMessage>(),
CancellationTokenSource = new CancellationTokenSource()
}
);
HttpResponse response = context.Response;
response.ContentType = CONTENT_TYPE;
response.CacheControl = "no-cache";
response.ContentEncoding = Encoding.UTF8;
response.AppendHeader("connection", "keep-alive");
response.BufferOutput = false;
bool supportsAsyncFlush = response.SupportsAsyncFlush;
bool shouldPing = true;
while (response.IsClientConnected) {
try {
NotificationMessage message = null;
if ((!item.Messages.IsEmpty && item.Messages.TryDequeue(out message)) || shouldPing) {
response.Write(string.Format("data:{0}\n\n", message == null ? "{}" : JsonMapper.Serialize(message)));
if (supportsAsyncFlush) {
await Task.Factory.FromAsync(response.BeginFlush, response.EndFlush, null);
} else {
response.Flush();
}
}
} catch (Exception) {
break;
}
var delay = Task.Delay(15000, item.CancellationTokenSource.Token);
await delay;
shouldPing = delay.Status == TaskStatus.RanToCompletion;
}
}
}
The problem is: the above doesn't works. I have two issues:
1) When the client connects, I receive an empty packet (that's ok). Then, if I don't enqueue any messages, after awaiting the Task.Delay, the loop tries to write an empty message again, but I don't know where. The response.Write line never returns (and nothing is being received on the client).
2) If I write to the queue, for some reason the connection is dropped. If I put a breakpoint on the line after the await delay, that line is never executed (while my logic says otherwise :) ). If I cancel the token, the delay task should quit, but it seems it is aborting the whole handler??