The below program sends the message to the queue, however most of the times the message is not sent.
I am using the Sender() method (as shown below) in my class library. This is being called from a Cloud Service(Worker Role). Please help to identify why the message is not consistently sent and correcting the code.
{
static void Main(string[] args)
{
Sender();
Console.ReadKey();
}
private static void Sender()
{
var ConnectionString = "<<Connectionstring>>";
var QueueName = "samplequeue";
var queueClient = new QueueClient(ConnectionString, QueueName);
try
{
for (var i = 0; i < 5; i++)
{
// Create a new message to send to the queue
string messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
// Write the body of the message to the console
Console.WriteLine($"Sending message: {messageBody}");
// Send the message to the queue
queueClient.SendAsync(message);
}
}
catch (Exception ex)
{
Console.WriteLine("Message queue failed\n" + ex.Message);
}
finally
{
Console.WriteLine("Message queued");
}
}
}
I believe the problem is that you're not awaiting the execution of an async method.
queueClient.SendAsync(message)
One option would be to change the above call and wait for the execution to complete
await queueClient.SendAsync(message);
In this case you will need to make Sender method async as well and then wait for it to complete in your Main method.
Other option would be to use Sync version of the method:
queueClient.Send(message);
You must wait on the call to complete. Otherwise it will be run in the background.
That means that Message 4 might complete after Message 3 (since you have a loop) or that it wont complete at all if your console application exits before it have a chance.
Since you are using a synchronous application, you can invkoe Send() like this:
queueClient.SendAsync(message).GetAwaiter().GetResult();
See .Result vs .GetAwaiter().GetResult()
Related
I created a .NET core 6 project. I added Azure.Messaging.ServiceBus as the dependency. I am using below code to send message to service bus topic.
// See https://aka.ms/new-console-template for more information
using Azure.Messaging.ServiceBus;
using System.Dynamic;
using System.Net;
using System.Text;
using System.Text.Json;
Console.WriteLine("Hello, World!");
Sender t = new Sender();
Sender.Send();
class Sender
{
public static async Task Send()
{
string connectionString = "Endpoint=sb://sb-test-one.servicebus.windows.net/;SharedAccessKeyName=manage;SharedAccessKey=8e+6SWp3skB3AeDlwH6ufGEainEs45353435JzDywz5DU=;";
string topicName = "topicone";
string subscriptionName = "subone";
// The Service Bus client types are safe to cache and use as a singleton for the lifetime
try
{
await using var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets
});
// create the sender
ServiceBusSender sender = client.CreateSender(topicName);
dynamic data = new ExpandoObject();
data.name = "Abc";
data.age = 6;
// create a message that we can send. UTF-8 encoding is used when providing a string.
var messageBody = JsonSerializer.Serialize(data);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
// send the message
await sender.SendMessageAsync(message);
var s = 10;
}
catch (Exception e)
{
var v = 10;
}
//// create a receiver for our subscription that we can use to receive the message
//ServiceBusReceiver receiver = client.CreateReceiver(topicName, subscriptionName);
//// the received message is a different type as it contains some service set properties
//ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
//// get the message body as a string
//string body = receivedMessage.Body.ToString();
//Console.WriteLine(body);
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
}
}
Issue: When I call await sender.SendMessageAsync(message); after this line get executed, the program is actually terminating. It not awating. The whole execution stops after this line.
System is not throwing any exception and service bus is not receiving any message.
I just noticed that all other samples I saw had a default SharedAccessPolicy called RootManageSharedAccessKey policy available by default in the azure portal. For me, I had to create this policy. To my policy I have given Manage, Send, ReceiveAccess.
Needed to change Sender.Send(); to Sender.Send().GetAwaiter().GetResult();
I have created below sample application to test "MaxAutoLockRenewalDuration" functionality.
Here is my test scenario,
Created two console application with the same code.
Add one message in the queue
Run Console App1 which receives the message
Run Console App2 which receives the same message.
With below code I have setup MaxAutoLockRenewalDuration to 10 minutes.
To my understanding of "MaxAutoLockRenewalDuration" it should renew the lock automatically till 10 minutes and second console app should not receive the same message.
public class Program
{
static string connectionString = "***";
static string queueName = "firstqueue";
static async Task Main(string[] args)
{
try
{
//uncomment below if you would like to add message to queue
//await CreateMessage(queueName, "Message 1 to test 'MaxAutoLockRenewalDuration'");
await ReceiveMessagesAsync();
}
catch (Exception ex)
{
throw;
}
Console.ReadKey();
}
private static async Task CreateMessage(string queueName, string textMessage)
{
// create a Service Bus client
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a sender for the queue
ServiceBusSender sender = client.CreateSender(queueName);
// create a message that we can send
ServiceBusMessage message = new ServiceBusMessage(textMessage);
// send the message
await sender.SendMessageAsync(message);
Console.WriteLine($"Sent a single message to the queue: {queueName}");
}
}
// handle received messages
static async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body}");
System.Threading.Thread.Sleep(TimeSpan.FromMinutes(5));
// complete the message. messages is deleted from the queue.
await args.CompleteMessageAsync(args.Message);
}
// handle any errors when receiving messages
static Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
static async Task ReceiveMessagesAsync()
{
var processorOptions = new ServiceBusProcessorOptions
{
AutoCompleteMessages = false,
MaxConcurrentCalls = 1,
MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(10),
ReceiveMode = ServiceBusReceiveMode.PeekLock,
PrefetchCount = 1
};
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a processor that we can use to process the messages
ServiceBusProcessor processor = client.CreateProcessor(queueName, processorOptions);
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
// stop processing
Console.WriteLine("\nStopping the receiver...");
await processor.StopProcessingAsync();
Console.WriteLine("Stopped receiving messages");
}
}
static async Task ReceiveMessagesAsync(string queueName)
{
await using (ServiceBusClient client = new ServiceBusClient(connectionString))
{
// create a processor that we can use to process the messages
ServiceBusProcessor processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
// stop processing
Console.WriteLine("\nStopping the receiver...");
await processor.StopProcessingAsync();
Console.WriteLine("Stopped receiving messages");
}
}
}
I'm using your code with Azure.Messaging.ServiceBus Version "7.1.1" and "7.1.2", the "MaxAutoLockRenewalDuration" functionality works well.
Here are my steps:
1.Send a message to the queue.
2.Run Console App1 which receives the message
3.Keep Console App1 running, and then run Console App2, there is no message received there. Here is the screenshot:
But if you first run Console App1 which receives the message, then close the Console App1 -> then run Console App2, you can see the message in Console App2. This is expected.
I have below code which works fine but when function got trigger it only read one message at a time.
to get more than 1 message i have given count 10 into ReceiveAsync(10) , but
still getting only one message.
public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
try
{
var deadQueuePath = EntityNameHelper.FormatDeadLetterPath("demo/subscriptions/demo");
MessageReceiver deadletterReceiver = new MessageReceiver(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), deadQueuePath, ReceiveMode.PeekLock,RetryPolicy.Default);
MessageSender sender = new MessageSender(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), "demo",RetryPolicy.Default);
var deadLetter = await deadletterReceiver.ReceiveAsync(10);
if (deadLetter != null)
{
log.LogInformation($"got new message");
Message newMessage = new Message(deadLetter.Body)
{
ContentType = deadLetter.ContentType,
CorrelationId = deadLetter.CorrelationId
};
//Send the message to the Active Queue
await sender.SendAsync(newMessage);
await deadletterReceiver.CompleteAsync(item.SystemProperties.LockToken); //Unlock the message and remove it from the DLQ
}
}
catch (Exception ex)
{
log.LogInformation($"Exception: {ex}");
}
}
ReceiveAsync(10) - This method is used for setting the sequence number of the message to receive and not the count. Look at the details on ReceiveAsync here
In order to fetch multiple messages, you need to set Prefetch property to the receiver.
Make use of the below constructor to initialize your MessageReceiver Class:
public MessageReceiver (Microsoft.Azure.ServiceBus.ServiceBusConnection serviceBusConnection, string entityPath, Microsoft.Azure.ServiceBus.ReceiveMode receiveMode = Microsoft.Azure.ServiceBus.ReceiveMode.PeekLock, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null, int prefetchCount = 0);
public static async Task DoMessage()
{
const int numberOfMessages = 10;
queueClient = new QueueClient(ConnectionString, QueueName);
await SendMessageAsync(numberOfMessages);
await queueClient.CloseAsync();
}
private static async Task SendMessageAsync(int numOfMessages)
{
try
{
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = i.ToString();
await queueClient.SendAsync(message);
}
}
catch (Exception e)
{
}
}
This is my sample code to send message to the service bus queue with session id.
My question is if I call DoMessage function 2 times: Let's name it as MessageSet1 and MessageSet2, respectively. Will the MessageSet2 be received and processed by the received azure function who dealing with the receiving ends of the message.
I want to handle in order like MessageSet1 then the MessageSet2 and never handle with MessageSet2 unless MessageSet1 finished.
There are a couple of issues with what you're doing.
First, Azure Functions do not currently support sessions. There's an issue for that you can track.
Second, the sessions you're creating are off. A session should be applied on a set of messages using the same SessionId. Meaning your for loop should be assigning the same SessionId to all the messages in the set. Something like this:
private static async Task SendMessageAsync(int numOfMessages, string sessionID)
{
try
{
var tasks = new List<Task>();
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = sessionId;
tasks.Add(queueClient.SendAsync(message));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (Exception e)
{
// handle exception
}
}
For ordered messages using Sessions, see documentation here.
When I declare a temporary reply queue to be exclusive (e.g. anonymous queue (exclusive=true, autodelete=true) in rpc-pattern), the response message cannot be posted to the specified reply queue (e.g. message.replyTo="amq.gen-Jg_tv8QYxtEQhq0tF30vAA") because RabbitMqProducer.PublishMessage() tries to redeclare the queue with different parameters (exclusive=false), which understandably results in an error.
Unfortunately, the erroneous call to channel.RegisterQueue(queueName) in RabbitMqProducer.PublishMessage() seems to nack the request message in the incoming queue so that, when ServiceStack.Messaging.MessageHandler.DefaultInExceptionHandler tries to acknowlege the request message (to remove it from the incoming queue), the message just stays on top of the incoming queue and gets processed all over again. This procedure repeats indefinitely and results in one dlq-message per iteration which slowly fills up the dlq.
I am wondering,
if ServiceStack handles the case, when ServiceStack.RabbitMq.RabbitMqProducer cannot declare the response queue, correctly
if ServiceStack.RabbitMq.RabbitMqProducer muss always declare the response queue before publishing the response
if it wouldn't be best to have some configuration flag to omit all exchange and queue declaration calls (outside of the first initialization). The RabbitMqProducer would just assume every queue/exchange to be properly set up and just publish the message.
(At the moment our client just declares its response queue to be exclusive=false and everything works fine. But I'd really like to use rabbitmq's built-in temporary queues.)
MQ-Client Code, requires simple "SayHello" service:
const string INQ_QUEUE_NAME = "mq:SayHello.inq";
const string EXCHANGE_NAME="mx.servicestack";
var factory = new ConnectionFactory() { HostName = "192.168.179.110" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// Create temporary queue and setup bindings
// this works (because "mq:tmp:" stops RabbitMqProducer from redeclaring response queue)
string responseQueueName = "mq:tmp:SayHello_" + Guid.NewGuid().ToString() + ".inq";
channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work (RabbitMqProducer tries to declare queue again => error):
//string responseQueueName = Guid.NewGuid().ToString() + ".inq";
//channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work either (RabbitMqProducer tries to declare queue again => error)
//var responseQueueName = channel.QueueDeclare().QueueName;
// publish simple SayHello-Request to standard servicestack exchange ("mx.servicestack") with routing key "mq:SayHello.inq":
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
channel.BasicPublish(EXCHANGE_NAME, INQ_QUEUE_NAME, props, Encoding.UTF8.GetBytes("{\"ToName\": \"Chris\"}"));
// consume response from response queue
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
// print result: should be "Hello, Chris!"
Console.WriteLine(Encoding.UTF8.GetString(ea.Body));
}
}
Everything seems to work fine when RabbitMqProducer does not try to declare the queues, like that:
public void PublishMessage(string exchange, string routingKey, IBasicProperties basicProperties, byte[] body)
{
const bool MustDeclareQueue = false; // new config parameter??
try
{
if (MustDeclareQueue && !Queues.Contains(routingKey))
{
Channel.RegisterQueueByName(routingKey);
Queues = new HashSet<string>(Queues) { routingKey };
}
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
catch (OperationInterruptedException ex)
{
if (ex.Is404())
{
Channel.RegisterExchangeByName(exchange);
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
throw;
}
}
The issue got adressed in servicestack's version v4.0.32 (fixed in this commit).
The RabbitMqProducer no longer tries to redeclare temporary queues and instead assumes that the reply queue already exist (which solves my problem.)
(The underlying cause of the infinite loop (wrong error handling while publishing response message) probably still exists.)
Edit: Example
The following basic mq-client (which does not use ServiceStackmq client and instead depends directly on rabbitmq's .net-library; it uses ServiceStack.Text for serialization though) can perform generic RPCs:
public class MqClient : IDisposable
{
ConnectionFactory factory = new ConnectionFactory()
{
HostName = "192.168.97.201",
UserName = "guest",
Password = "guest",
//VirtualHost = "test",
Port = AmqpTcpEndpoint.UseDefaultPort,
};
private IConnection connection;
private string exchangeName;
public MqClient(string defaultExchange)
{
this.exchangeName = defaultExchange;
this.connection = factory.CreateConnection();
}
public TResponse RpcCall<TResponse>(IReturn<TResponse> reqDto, string exchange = null)
{
using (var channel = connection.CreateModel())
{
string inq_queue_name = string.Format("mq:{0}.inq", reqDto.GetType().Name);
string responseQueueName = channel.QueueDeclare().QueueName;
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
var message = ServiceStack.Text.JsonSerializer.SerializeToString(reqDto);
channel.BasicPublish(exchange ?? this.exchangeName, inq_queue_name, props, UTF8Encoding.UTF8.GetBytes(message));
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//channel.BasicAck(ea.DeliveryTag, false);
string response = UTF8Encoding.UTF8.GetString(ea.Body);
string responseType = ea.BasicProperties.Type;
Console.WriteLine(" [x] New Message of Type '{1}' Received:{2}{0}", response, responseType, Environment.NewLine);
return ServiceStack.Text.JsonSerializer.DeserializeFromString<TResponse>(response);
}
}
~MqClient()
{
this.Dispose();
}
public void Dispose()
{
if (connection != null)
{
this.connection.Dispose();
this.connection = null;
}
}
}
Key points:
client declares anonymous queue (=with empty queue name) channel.QueueDeclare()
server generates queue and returns queue name (amq.gen*)
client adds queue name to message properties (props.ReplyTo = responseQueueName;)
ServiceStack automatically sends response to temporary queue
client picks up response and deserializes
It can be used like that:
using (var mqClient = new MqClient("mx.servicestack"))
{
var pingResponse = mqClient.RpcCall<PingResponse>(new Ping { });
}
Important: You've got to use servicestack version 4.0.32+.