how to respond to an event on a different thread - c#

I have implemented a rabbitMQ listener, which essentially just sits in a loop waiting for messages to arrive. When the message arrives I wish to fire an event and run some code.
However I don't always seem to receive this event, and I am wondering if this is because I am running the queue polling part of the code on a different thread.
It does seem to work initially though, so it is possible that the threading is not the problem. Can anyone give me an opinion on this?
QueueListener:
public void CreateQueueListener<T>() where T : IPubSubEvent
{
var mqServer = new RabbitMqServer(m_RabbitMqAddress);
var mqClient = (RabbitMqQueueClient)mqServer.MessageFactory.CreateMessageQueueClient();
var channel = mqClient.Channel;
string queueName = mqClient.GetTempQueueName();
channel.QueueBind(queueName, m_EventExchange, routingKey: QueueNames<T>.In);
var consumer = new RabbitMqBasicConsumer(channel);
channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Task.Run(() =>
{
while (true)
{
BasicGetResult basicGetResult;
try
{
basicGetResult = consumer.Queue.Dequeue();
}
catch (Exception ex)
{
throw;
}
var message = basicGetResult.ToMessage<T>();
PublishEvent?.Invoke(this, new PubSubEventArgs { EventData = message.GetBody().EventName });
}
});
}
Consuming Class
public class MyClass
{
public MyClass(IEventClient eventClient)
{
eventClient.CreateQueueListener<AuthoriseEvent>();
eventClient.PublishEvent += OnPublishEvent;
}
private async void OnPublishEvent(object sender, PubSubEventArgs e)
{
if (e.EventData == "AuthoriseEvent")
//dostuff
}
}

I am running the queue polling part of the code on a different thread
As far as I know, this isn't supported by the .NET client.
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.

Related

C# mailkit imap client idle mode not getting cancelled

when new email is received, the idle stop method is ran and there it says Imap client is currently busy processing in another thread. i assume its because idle command is still running in the background thread ?
even tho i called thread.Join() method, it wont end. i am sturck here for quite some time, and the example demo on MailKit github only shows how to handle it with help from manual user input such as Console.ReadKey().
i am pretty sure i am missing some major point or the code have major flaws but i have searched many times for an answer and there doesnt seem to be any major results other than the github example
protocol logger when idle start and message recevied until the exception occurs
S: * OK [UIDNEXT 21641] Predicted next UID.
S: * OK [HIGHESTMODSEQ 881089]
S: A00000006 OK [READ-WRITE] INBOX selected. (Success)
C: A00000007 IDLE
S: + idling
S: * 21512 EXISTS
C: DONE
method which starts idle
IdleClient.Inbox.MessageExpunged += OnMessageExpunged;
IdleClient.Inbox.CountChanged += OnInboxCountChanged;
ImapToken = new CancellationTokenSource();
SetTokenValues(ImapToken.Token);
ImapToken.Token.ThrowIfCancellationRequested();
ImapThreadInfo = Helpers.InBackgroundThread(ImapIdleLoop, UniqueAccountId, true);
declarations related to idle
private (int, Thread) ImapThreadInfo;
private CancellationToken CancellationToken { get; set; }
private CancellationToken DoneToken { get; set; }
private CancellationTokenSource ImapToken { get; set; }
private CancellationTokenSource Timeout { get; set; }
private bool IsCancellationRequested => CancellationToken.IsCancellationRequested || DoneToken.IsCancellationRequested;
private readonly object Mutex = new object();
private void CancelTimeout() {
lock (Mutex) {
Timeout?.Cancel();
}
}
private void SetTimeoutSource(CancellationTokenSource source) {
lock (Mutex) {
Timeout = source;
if (Timeout != null && IsCancellationRequested) {
Timeout.Cancel();
}
}
}
private void SetTokenValues(CancellationToken doneToken, CancellationToken cancellationToken = default) {
CancellationToken = cancellationToken;
DoneToken = doneToken;
doneToken.Register(CancelTimeout);
}
stop idle method
public void StopImapIdle(bool clientDisconnect) {
ImapToken.Cancel();
try {
Task.Factory.StartNew(() => {
ImapThreadInfo.Item2?.Join();
});
ImapToken.Dispose();
if (!clientDisconnect) {
return;
}
if (IdleClient.IsConnected && IdleClient.IsIdle) {
while (true) {
if (!IdleClient.IsIdle) {
BotLogger.Log("Idling has been stopped.", LogLevels.Trace);
break;
}
BotLogger.Log("Waiting for idle client to stop idling...", LogLevels.Trace);
}
}
lock (IdleClient.SyncRoot) {
//Error here
IdleClient.Disconnect(true);
BotLogger.Log("Imap client has been disconnected.", LogLevels.Trace);
}
}
catch (NullReferenceException) {
BotLogger.Log("There is no thread with the specified uniqueID", LogLevels.Warn);
}
IsAccountLoaded = false;
}
idle loop method
private void ImapIdleLoop() {
while (!IsCancellationRequested) {
Timeout = new CancellationTokenSource(new TimeSpan(0, 9, 0));
try {
SetTimeoutSource(Timeout);
if (IdleClient.Capabilities.HasFlag(ImapCapabilities.Idle)) {
lock (IdleClient.SyncRoot) {
IdleClient.Idle(Timeout.Token, CancellationToken);
}
}
else {
lock (IdleClient.SyncRoot) {
IdleClient.NoOp(CancellationToken);
}
WaitHandle.WaitAny(new[] { Timeout.Token.WaitHandle, CancellationToken.WaitHandle });
}
}
catch (OperationCanceledException) {
// This means that idle.CancellationToken was cancelled, not the DoneToken nor the timeout.
break;
}
catch (ImapProtocolException) {
// The IMAP server sent garbage in a response and the ImapClient was unable to deal with it.
// This should never happen in practice, but it's probably still a good idea to handle it.
//
// Note: an ImapProtocolException almost always results in the ImapClient getting disconnected.
IsAccountLoaded = false;
break;
}
catch (ImapCommandException) {
// The IMAP server responded with "NO" or "BAD" to either the IDLE command or the NOOP command.
// This should never happen... but again, we're catching it for the sake of completeness.
break;
}
catch (SocketException) {
}
catch (ServiceNotConnectedException) {
}
catch (IOException) {
}
finally {
// We're about to Dispose() the timeout source, so set it to null.
SetTimeoutSource(null);
}
Timeout?.Dispose();
}
}
The problem is that you are waiting for the thread to join from within the ImapClient's event callback which means you are blocking the ImapClient from continuing.
The solution is: don't do that.
MailKit's IMAP events are emitted while the IMAP command processor is still processing the server responses, so you cannot invoke more commands on the same ImapClient within those event handlers.
What you need to do instead is to implement some sort of command queue in your program and then, within the CountChanged event handler (or whatever handler you are handling), queue the next command(s) to invoke once the current command completes.
An easy way to do this is to keep a System.Threading.Tasks.Task somewhere where your event handler has access to it and can then do:
task = task.ContinueWith (...);
That's a simple way of implementing a command queue.

.NET Client - Waiting for an MQTT response before proceeding to the next request

I have a MQTT calls inside a loop and in each iteration, it should return a response from the subscriber so that I could use the value being forwarded after I published. But the problem is I don't know how would I do it.
I hope you have an idea there or maybe if I'm just not implementing it right, may you guide me through this. Thanks.
Here's my code:
// MyClientMgr
class MyClientMgr{
public long CurrentOutput { get; set; }
public void GetCurrentOutput(MyObjectParameters parameters, MqttClient client)
{
MyMessageObject msg = new MyMessageObject
{
Action = MyEnum.GetOutput,
Data = JsonConvert.SerializeObject(parameters)
}
mq_GetCurrentOutput(msg, client);
}
private void mq_GetCurrentOutput(MyMessageObject msg, MqttClient client)
{
string msgStr = JsonConvert.SerializeObject(msg);
client.Publish("getOutput", Encoding.UTF8.GetBytes(msgStr),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
client.MqttMsgPublishReceived += (sender, e) =>{
MyObjectOutput output = JsonConvert.DeserializeObject<MyObjectOutput>(Encoding.UTF8.GetString(e.Message));
CurrentOutput = output;
};
}
}
// MyServerMgr
class MyServerMgr
{
public void InitSubscriptions()
{
mq_GetOutput();
}
private void mq_GetOutput()
{
MqttClient clientSubscribe = new MqttClient(host);
string clientId = Guid.NewGuid().ToString();
clientSubscribe.Connect(clientId);
clientSubscribe.Subscribe(new string[] { "getOutput" }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
MqttClient clientPublish = new MqttClient(host);
string clientIdPub = Guid.NewGuid().ToString();
clientPublish.Connect(clientIdPub);
clientSubscribe.MqttMsgPublishReceived += (sender, e) => {
MyMessageObj msg = JsonConvert.DeserializeObject<MyMessageObj>(Encoding.UTF8.GetString(e.Message));
var output = msg.Output;
clientPublish.Publish("getOutput", Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(output)), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
}
}
// MyCallerClass
class MyCallerClass
{
var host = "test.mqtt.org";
var myServer = new MyServerMgr(host);
var myClient = new MyClientMgr();
myServer.InitSubscriptions();
MqttClient client = new MqttClient(host);
for(int i = 0; i < 10; i++)
{
long output = 0;
MyObjectParameters parameters = {};
myClient.GetCurrentOutput(parameters, client) // here I call the method from my client manager
// to publish the getting of the output and assigned
// below for use, but the problem is the value doesn't
// being passed to the output variable because it is not
// yet returned by the server.
// Is there a way I could wait the process to
// get the response before assigning the output?
output = myClient.CurrentOutput; // output here will always be null
// because the response is not yet forwarded by the server
}
}
I have a loop in my caller class to call the mqtt publish for getting the output, but I have no idea how to get the output before it was assigned, I want to wait for the response first before going to the next.
I've already tried doing a while loop inside like this:
while(output == 0)
{
output = myClient.CurrentOutput;
}
Yes, I can get the output here, but it will slow down the process that much. And sometimes it will fail.
Please help me. Thanks.
It looks like you are trying to do synchronous communication over an asynchronous protocol (MQTT).
By this I mean you want to send a message and then wait for a response, this is not how MQTT works as there is no concept of a reply to a message at the protocol level.
I'm not that familiar with C# so I'll just give an abstract description of possible solution.
My suggestion would be to use a publishing thread, wait/pulse (Look at the Monitor class) to have this block after each publish and have the message handler call pulse when it has received the response.
If the response doesn't contain a wait to identify the original request you will also need a state machine variable to record which request is in progress.
You may want to look at putting a time out on the wait in case the other end does not respond for some reasons.
You can use AutoResetEvent class that has WaitOne() and Set() methods. Using WaitOne() after publish will wait until the message is published and using Set() under client_MqttMsgPublishReceived event will release the wait when the subscriber received the message he subscribed for.

RX UDPClient that listeners for messages from devices from a list of ports and subscribes to data to post to Azure Event Hub

I have some code which rolls through a list of ports and sets up a stream containing the UDP Buffer contents which can be subscribed to.
I'm pretty new to RX and this was built with some help in response to another stackoverflow question.
This code seemed to be OK in early tests and handled all the packets we could send it but now it's in soak test the subscription seems to fail after a while and it stops sending events to the event hub.
I'm particularly worried that I have an exception somewhere in the subscription code which causes me to lose the information but isn't caught.
Here is the code, any comments or suggestions for simplification or improvement would be greatly appreciated:
var receiveStream =
_deviceTypeProvider.GetDeviceTypes().ToObservable().Trace("GetDeviceTypes")
.SelectMany(devicePortMapping => Observable
.Using(() => UdpListener(devicePortMapping),
client =>
Observable
.FromAsync(client.ReceiveAsync)
.Repeat()).Trace("UdpListener"),
(devicePortMapping, stream) =>
{
Log
.ForContext("Raw", stream.Buffer.ToPrintByteArray())
.Verbose("Received Incoming {DeviceType} Message from {Device} on Port {Port}",
devicePortMapping.DeviceType, stream.RemoteEndPoint.Address, devicePortMapping.Port);
try
{
var timeZoneOffset =
_deviceTimeZoneOffsetProvider.GetDeviceTimeZoneOffset(devicePortMapping.Port);
var tenant = _deviceTenantProvider.GetDeviceTenant(devicePortMapping.Port);
if (tenant == null || timeZoneOffset == null)
{
Log
.Error(
"Tenant or TimeOffset Missing for Port: {Port}, cannot continue processing this message",
devicePortMapping.Port);
return null;
}
var message =
new DeviceMessage(new Device(stream.RemoteEndPoint.Address.ToIPAddressNum(),
stream.RemoteEndPoint.Port, devicePortMapping.DeviceType, tenant.TenantId,
timeZoneOffset.Offset))
{
MessageUid = Guid.NewGuid(),
Timestamp = DateTime.UtcNow,
Raw = stream.Buffer,
};
message.Information("Received Incoming Message");
return message;
}
catch (Exception ex)
{
Log.Error(ex, "Exception whilst receiving incoming message");
throw;
}
}).Trace("SelectMany").Select(Task.FromResult).Trace("Select");
if (_settings.TestModeEnabled)
{
Log
.Warning("Test Mode is Enabled");
receiveStream = receiveStream
.Select(async message =>
await _testModeProvider.InjectTestModeAsync(await message)).Trace("TestMode");
}
_listener = receiveStream.Subscribe(async messageTask =>
{
var message = await messageTask;
if (message == null)
{
Log
.Warning("Message is null, returning");
return;
}
Log
.ForContext("Raw", message.Raw.ToPrintByteArray(), true)
.ForContext("Device", message.Device, true)
.Verbose("Publishing Message {MessageUid} from {#Device}", message.MessageUid, message.Device);
await _messagePublisher.Publish(message).ConfigureAwait(false);
}, error => { Log.Error(error, "Exception whilst publishing message"); });
Here is the InjectTestMode method:
public async Task<DeviceMessage> InjectTestModeAsync(DeviceMessage deviceMessage)
{
try
{
var imei = GetImei(deviceMessage.Raw);
if (string.IsNullOrEmpty(imei))
{
Log
.ForContext("DeviceMessage",deviceMessage,true)
.Error("Error while getting IMEI value from message raw data in Test Mode");
return null;
}
//var dummyIpAddress = DummyIPfromIMEI(imei).ToIPAddressNum();
var mapping = await _mappingService.GetIPMappingAsync(deviceMessage.Device.IPAddress);
if (mapping == null)
{
Log
.ForContext("DeviceMessage", deviceMessage, true)
.Warning("Test Mode updated IP Address mapping with IPAddress: {IPAddress} for IMEI: {IMEI}", deviceMessage.Device.IPAddress.ToIPAddressString(), imei);
await _mappingService.UpdateIPMappingAsync(deviceMessage.Device.IPAddress, imei);
}
// deviceMessage.Device.IPAddress = dummyIpAddress;
return deviceMessage;
}
catch (Exception ex)
{
Log
.ForContext("DeviceMessage",deviceMessage,true)
.Error("Exception raised whilst injecting Test Mode", ex);
return null;
}
}
Here is the UdpListener method:
private UdpClient UdpListener(DeviceTypeMap deviceTypeMap)
{
Log.Information("Listening for Device Type: {DeviceType} messages on Port: {Port}", deviceTypeMap.DeviceType,
deviceTypeMap.Port);
var udpClient = new UdpClient();
var serverEndpoint = new IPEndPoint(IPAddress.Any, deviceTypeMap.Port);
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Client.Bind(serverEndpoint);
return udpClient;
}
[Update] - 16/11/2015
So I've done some research and it would seem I have more than one code smell in this so I've updated the code and it's now running but I thought I'd share the intent of this code and well as the revised code to see if someone can suggest a more elegant solution.
The Intent
Listen to several UDP Ports and send the traffic to Azure Event Hubs along with some meta based on the port it was received on.
This meta would depend on whether the system was in a 'Test Mode' or not.
The Implementation
For each Port and Device Type create a UDPListener and Observable Collection from the ReceiveAsync event, combine these Observable Collections into a single collection which could then be subscribed from component which would publish the data to EventHubs.
The Problems
The InjectMode was async, and this could return a null if there was a problem, this seemed to be killing the sequence. I think that this probably should have been some sort of Observable Extension Method which allow as to modify or remove the device message from the sequence but I couldn't figure this out.
Originally the publish to EventHub was in the subscription until I read that that you shouldn't use async inside a subscription as it generates a async void which is bad. All the research seemed to point to pushing this into a SelectMany which did'nt make any sense since this was the destination of the observed sequence not part of the process but I went with this. The subscription in effect became redundant.
I not sure that all of the try catch blocks were required but I was convinced that I had a problem which was disrupted the sequence. As noted by Enigmativity I made these all Exception catches and logged then and re-throw, nothing ever appeared from these log entries.
Retry().Do() doesn't feel right, I could get SelectMany() as suggested in many other posts to work so I had no choice.
Here's the code which is running now :
public void Start()
{
Log.Information("InboundUdpListener is starting");
var receiveStream =
_deviceTypeProvider.GetDeviceTypes().ToObservable().Trace("GetDeviceTypes")
.SelectMany(devicePortMapping => Observable
.Using(() => UdpListener(devicePortMapping),
client =>
Observable
.FromAsync(client.ReceiveAsync)
.Repeat()).Trace("UdpListener"),
(devicePortMapping, stream) =>
{
Log
.ForContext("Raw", stream.Buffer.ToPrintByteArray())
.Verbose("Received Incoming {DeviceType} Message from {Device} on Port {Port}",
devicePortMapping.DeviceType, stream.RemoteEndPoint.Address, devicePortMapping.Port);
try
{
var timeZoneOffset =
_deviceTimeZoneOffsetProvider.GetDeviceTimeZoneOffset(devicePortMapping.Port);
var tenant = _deviceTenantProvider.GetDeviceTenant(devicePortMapping.Port);
if (tenant == null || timeZoneOffset == null)
{
Log
.Error(
"Tenant or TimeOffset Missing for Port: {Port}, cannot continue processing this message",
devicePortMapping.Port);
return null;
}
var message =
new DeviceMessage(new Device(stream.RemoteEndPoint.Address.ToIPAddressNum(),
stream.RemoteEndPoint.Port, devicePortMapping.DeviceType, tenant.TenantId,
timeZoneOffset.Offset))
{
MessageUid = Guid.NewGuid(),
Timestamp = DateTime.UtcNow,
Raw = stream.Buffer,
};
message.Information("Received Incoming Message");
return message;
}
catch (Exception ex)
{
Log.Error(ex, "Exception whilst receiving incoming message");
throw;
}
}).Trace("SelectMany");
receiveStream = receiveStream.Retry().Do(async message =>
{
try
{
if (_testModeEnabled && message != null)
{
message = await _testModeProvider.InjectTestModeAsync(message);
}
if (message != null)
{
await _messagePublisher.Publish(message);
}
}
catch (Exception ex)
{
Log.Error(ex, "Exception whilst publishing incoming message");
throw;
}
}).Trace("Publish");
_listener = receiveStream.Retry().Subscribe(OnMessageReceive, OnError, OnComplete);
Log.Information("InboundUdpListener is started");
}
Can anyone see any issues with this code or suggest any improvements. I really would appreciate some help with this.
[Update Following Lee's Comment]
I totally agree that it was a mess and to show I'm willing to learn take on board people's help this is my next attempt
public void Start()
{
_listener = _deviceTypeProvider.GetDeviceTypes().ToObservable()
.SelectMany(CreateUdpListener, CreateMessage)
.SelectMany(InjectTestMode)
.SelectMany(PublishMessage)
.Retry()
.Subscribe(OnMessageReceive, OnError, OnComplete);
}
private IObservable<UdpReceiveResult> CreateUdpListener(DeviceTypeMap deviceType)
{
return Observable.Using(() => UdpListener(deviceType),
client => Observable.FromAsync(client.ReceiveAsync).Repeat());
}
private DeviceMessage CreateMessage(DeviceTypeMap deviceTypeMap, UdpReceiveResult receiveResult)
{
var timeZoneOffset =
_deviceTimeZoneOffsetProvider.GetDeviceTimeZoneOffset(deviceTypeMap.Port);
var tenant = _deviceTenantProvider.GetDeviceTenant(deviceTypeMap.Port);
if (tenant == null || timeZoneOffset == null)
{
Log
.Error(
"Tenant or TimeOffset Missing for Port: {Port}, cannot continue processing this message",
deviceTypeMap.Port);
return null;
}
var message =
new DeviceMessage(new Device(receiveResult.RemoteEndPoint.Address.ToIPAddressNum(),
receiveResult.RemoteEndPoint.Port, deviceTypeMap.DeviceType, tenant.TenantId,
timeZoneOffset.Offset))
{
MessageUid = Guid.NewGuid(),
Timestamp = DateTime.UtcNow,
Raw = receiveResult.Buffer,
};
message.Information("Received Incoming Message");
return message;
}
private async Task<DeviceMessage> InjectTestMode(DeviceMessage message)
{
if (_testModeEnabled && message != null)
{
message = await _testModeProvider.InjectTestModeAsync(message);
}
return message;
}
private async Task<DeviceMessage> PublishMessage(DeviceMessage message)
{
await _messagePublisher.Publish(message);
return message;
}
private void OnComplete()
{
throw new NotImplementedException();
}
private void OnError(Exception ex)
{
throw new NotImplementedException();
}
private void OnMessageReceive(object o)
{
throw new NotImplementedException();
}
[Final Update]
This is what we finally ended up with; an IObservable>
var listeners = Observable.Defer(() => _deviceTypeProvider.GetDeviceTypes()
.ToObservable()
.Select(UdpListener)
.SelectMany(listener =>
{
return Observable.Defer(() => Observable
.FromAsync(listener.UdpClient.ReceiveAsync)
.Where(x => x.Buffer.Length > 0)
.Repeat()
.Select(result => CreateMessage(listener.DeviceType, result))
.SelectMany(InjectTestMode)
.OfType<DeviceMessage>()
.Do(async message => await PublishMessage(message)))
.Retry();
})).Retry();
_listener = listeners.Subscribe(OnMessageReceive, OnError, OnComplete);
So I can post some code and be constructive, this is what I think the query in your Start method should look like:
_listener = _deviceTypeProvider.GetDeviceTypes().ToObservable()
.SelectMany(CreateUdpListener, CreateMessage)
.Retry()
.Subscribe(OnMessageReceive, OnError, OnComplete);
Now the poor guy to come along and support this code has a chance of understanding it. :-)
I think you're missing the fact that RX sequences do terminate on exceptions. You need to use Observable.Catch if you want to continue sequence when exception occur.
See this http://www.introtorx.com/content/v1.0.10621.0/11_AdvancedErrorHandling.html

What is an elegant way of halting consumption of messages gracefully in the C# client for RabbitMQ?

I am setting up a standard standalone thread listening to RabbitMQ in C#. Suppose the method for listening in the thread looks like this:
public void Listen()
{
using (var channel = connection.CreateModel())
{
var consumer = SetupQueues(channel);
while (true)
{
var ea = consumer.Queue.Dequeue(); // blocking call
handler.HandleMessage(channel, ea);
}
}
}
What is an elegant way of halting consumption of messages gracefully in the C# client for RabbitMQ? Keep in mind I have found nothing of use in the RabbitMQ examples/docs or these SO questions:
How to stop consuming message from selective queue - RabbitMQ
How to pause and resume consumption gracefully in rabbitmq, pika python
What is the best way to safely end a java application with running RabbitMQ consumers
The issue here is consumer.Queue.Dequeue() is a blocking call. I have tried these options:
Calling channel.BasicCancel(string tag). This causes a System.IO.EndOfStreamException in the blocking call. I do not want to use this exception as part of the control flow for obvious reasons.
Calling consumer.Queue.Dequeue(int millisecondsTimeout, out T result) and checking a flag in between loop iterations. This can work but seems hacky.
I want to let the thread exit gracefully and clean up any unmanaged resources I might have, so no thread aborting, etc.
Any help is appreciated. Thanks
The DeQueue with the timeout & flag is the way to do it. It's a very common pattern, and is why many blocking calls are provided with timeout-enabled versions.
Alternately, throwing a (known) exception isn't necessarily a bad thing for control flow. Gracefully shutting down could mean actually trapping the exception, commenting "this is thrown when requesting the channel shuts down", and then returning cleanly. This is how part of TPL works with the CancellationToken.
The blocking methods are not property event-driven.
I don't why they suggest to use consumer.Queue.Dequeue();
Anyway, I usually don't use consumer.Queue.Dequeue();
I extend the default consumer, in this way:
class MyConsumer : DefaultBasicConsumer {
public MyConsumer(IModel model):base(model)
{
}
public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties properties, byte[] body) {
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
}
}
class Program
{
static void Main(string[] args)
{
var factory = new ConnectionFactory() { Uri = "amqp://aa:bbb#lemur.cloudamqp.com/xxx" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("hello", false, false, false, null);
var consumer = new MyConsumer(channel);
String tag = channel.BasicConsume("hello", true, consumer);
Console.WriteLine(" [*] Waiting for messages." +
" any key to exit");
Console.ReadLine();
channel.BasicCancel(tag);
/*while (true)
{
/////// DON'T USE THIS
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
}*/
}
}
}
}
In this way you don't have a blocking method, and you can release all the resources correctly.
EDIT
I think using ctrl+C to break a program is always wrong.

how can i get all the available messages on a MSMQ Queue

Whats the best way to get all the messages currently on a queue to process?
We have a queue with a large number of very small messages, what I would like to do is read all the current messages off and then send them through a thread pool for processing.
I can't seem to find any good resource which will show me how i can create a simple method to return an IEnnumerable for example
Thanks
Although I agree with Nick that the queue's purpose is more for FIFO style processing, and ArsenMkrt's solution will work, another option involves using a MessageEnumerator and piling the messages into your IEnumerable.
var msgEnumerator = queue.GetMessageEnumerator2();
var messages = new List<System.Messaging.Message>();
while (msgEnumerator.MoveNext(new TimeSpan(0, 0, 1)))
{
var msg = queue.ReceiveById(msgEnumerator.Current.Id, new TimeSpan(0, 0, 1));
messages.Add(msg);
}
For simple stuff...
public void DoIt()
{
bool continueToSeekForMessages = true;
while (continueToSeekForMessages)
{
try
{
var messageQueue = new System.Messaging.MessageQueue(#"FormatName:Direct=OS:MyComputerNameHere\Private$\MyPrivateQueueNameHere");
var message = messageQueue.Receive(new TimeSpan(0, 0, 3));
message.Formatter = new System.Messaging.XmlMessageFormatter(new String[] { "System.String,mscorlib" });
var messageBody = message.Body;
}
catch (Exception ex)
{
continueToSeekForMessages = false;
}
}
}
.
Also, could use peek instead of taking the message off the queue.
Also, could use GetMessageEnumerator2
Doesn't that defeat the purpose of the queue? The queue is supposed to keep the order of the messages, so you have to loop through and keep pulling the first message off.
Latest versions of MSMQ also has the following feature:
You can get all messages in a single object such as follows: (Write your own "ReceiveCompleted event" handler)
private static void MyReceiveCompleted(Object source,
ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
try
{
Message[] mm = mq.GetAllMessages();
foreach (Message m in mm)
{
// do whatever you want
}
}
catch (MessageQueueException me)
{
Console.WriteLine(me.Message);
}
finally
{
}
return;
}

Categories

Resources