Service Bus Receiving Messages
Receiving messages from Azure Service Bus means reading messages from a queue or subscription, processing them in the application, and confirming the result back to Service Bus. The confirmation step is critical — it tells Service Bus whether to delete the message or retry it. This topic covers both push-based and pull-based receiving models, message settlement options, and best practices.
Two Ways to Receive Messages
| Method | How It Works | Best For |
|---|---|---|
| Pull (Polling) | Application calls ReceiveMessageAsync() on demand | Low-volume, on-demand processing |
| Push (Processor) | Service Bus calls a handler function automatically when a message arrives | High-volume, continuous processing |
Receive Modes
| Mode | Behavior | Risk |
|---|---|---|
| Peek-Lock (default) | Lock the message, process it, then settle it | Low — message is safe until explicitly settled |
| Receive-and-Delete | Delete the message the moment it is received | High — if the app crashes mid-processing, message is lost |
Message Settlement Options
After reading a message in Peek-Lock mode, the receiver must settle it. Settlement tells Service Bus what to do with the message next.
| Settlement Action | Method | Result |
|---|---|---|
| Complete | CompleteMessageAsync() | Message is deleted from queue — success |
| Abandon | AbandonMessageAsync() | Message is returned to queue — another consumer can retry |
| Dead-Letter | DeadLetterMessageAsync() | Message moves to Dead Letter Queue — no more retries |
| Defer | DeferMessageAsync() | Message is parked with its SequenceNumber — must be fetched explicitly later |
Receiver reads message (lock starts)
|
| App processes message
|
|-- SUCCESS --> CompleteMessageAsync() --> Message DELETED
|
|-- RETRY --> AbandonMessageAsync() --> Message back in QUEUE
|
|-- POISON --> DeadLetterMessageAsync() --> Message in DLQ
|
'-- WAIT --> DeferMessageAsync() --> Message PARKED (fetch later)
Pull-Based Receiving — .NET SDK
Receive a Single Message
using Azure.Messaging.ServiceBus;
string connectionString = "Endpoint=sb://myshopns.servicebus.windows.net/;...";
string queueName = "orders";
await using var client = new ServiceBusClient(connectionString);
await using var receiver = client.CreateReceiver(queueName);
// Wait up to 5 seconds for a message
ServiceBusReceivedMessage message = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(5));
if (message != null)
{
Console.WriteLine($"Received: {message.Body}");
Console.WriteLine($"MessageId: {message.MessageId}");
Console.WriteLine($"DeliveryCount: {message.DeliveryCount}");
// Mark message as successfully processed
await receiver.CompleteMessageAsync(message);
Console.WriteLine("Message completed.");
}
else
{
Console.WriteLine("No messages in queue.");
}
Receive Multiple Messages (Batch Pull)
// Receive up to 10 messages at once
IReadOnlyList<ServiceBusReceivedMessage> messages =
await receiver.ReceiveMessagesAsync(maxMessages: 10, maxWaitTime: TimeSpan.FromSeconds(5));
foreach (var msg in messages)
{
try
{
Console.WriteLine($"Processing: {msg.Body}");
// ... business logic ...
await receiver.CompleteMessageAsync(msg);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}. Abandoning message.");
await receiver.AbandonMessageAsync(msg);
}
}
Receive from a Topic Subscription
// Receive from a specific subscription
await using var receiver = client.CreateReceiver("order-events", "inventory-sub");
var message = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));
if (message != null)
{
Console.WriteLine($"Order event: {message.Body}");
await receiver.CompleteMessageAsync(message);
}
Push-Based Receiving — ServiceBusProcessor (.NET)
The ServiceBusProcessor continuously listens for messages and calls a handler function each time a message arrives. This is the recommended approach for production services.
await using var client = new ServiceBusClient(connectionString);
var processorOptions = new ServiceBusProcessorOptions
{
MaxConcurrentCalls = 5, // Process up to 5 messages simultaneously
AutoCompleteMessages = false // Manually complete messages for safety
};
await using var processor = client.CreateProcessor("orders", processorOptions);
// Handler called when a message arrives
processor.ProcessMessageAsync += async args =>
{
var body = args.Message.Body.ToString();
Console.WriteLine($"Processing order: {body}");
// Business logic here...
await args.CompleteMessageAsync(args.Message);
Console.WriteLine("Message completed.");
};
// Handler called when an error occurs
processor.ProcessErrorAsync += async args =>
{
Console.WriteLine($"Error: {args.Exception.Message}");
Console.WriteLine($"Source: {args.ErrorSource}");
await Task.CompletedTask;
};
await processor.StartProcessingAsync();
Console.WriteLine("Processor running. Press Enter to stop.");
Console.ReadLine();
await processor.StopProcessingAsync();
How the Processor Works
[Queue: orders]
|
| Service Bus pushes messages automatically
v
[ServiceBusProcessor]
|
|-- message 1 --> [ProcessMessageAsync handler] --> Complete
|-- message 2 --> [ProcessMessageAsync handler] --> Complete
|-- message 3 --> [ProcessMessageAsync handler] --> Abandon (retry)
|
|-- error occurs --> [ProcessErrorAsync handler] --> log error
Pull-Based Receiving — Python SDK
Receive a Single Message
from azure.servicebus import ServiceBusClient
connection_str = "Endpoint=sb://myshopns.servicebus.windows.net/;..."
queue_name = "orders"
with ServiceBusClient.from_connection_string(connection_str) as client:
with client.get_queue_receiver(queue_name, max_wait_time=5) as receiver:
for msg in receiver:
print(f"Received: {str(msg)}")
receiver.complete_message(msg)
break # Process one message then stop
Receive and Abandon on Failure — Python
with ServiceBusClient.from_connection_string(connection_str) as client:
with client.get_queue_receiver(queue_name, max_wait_time=5) as receiver:
for msg in receiver:
try:
print(f"Processing: {str(msg)}")
# Business logic...
receiver.complete_message(msg)
except Exception as e:
print(f"Error: {e}. Abandoning.")
receiver.abandon_message(msg)
Message Lock Renewal
The default lock duration is 60 seconds. If processing takes longer, the lock expires and another consumer can pick up the same message — causing duplicate processing. Use lock renewal to extend the lock while processing continues.
// .NET — renew lock every 30 seconds during long processing
var message = await receiver.ReceiveMessageAsync();
using var cts = new CancellationTokenSource();
var renewTask = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
await receiver.RenewMessageLockAsync(message);
Console.WriteLine("Lock renewed.");
}
});
// Simulate long processing
await Task.Delay(TimeSpan.FromMinutes(3));
cts.Cancel();
await receiver.CompleteMessageAsync(message);
Dead-Lettering a Message Manually
var message = await receiver.ReceiveMessageAsync();
if (message.Body.ToString().Contains("INVALID"))
{
await receiver.DeadLetterMessageAsync(
message,
deadLetterReason: "InvalidPayload",
deadLetterErrorDescription: "Message body failed schema validation."
);
Console.WriteLine("Message dead-lettered.");
}
Receiving Best Practices
- Always use Peek-Lock mode for critical messages — avoid Receive-and-Delete
- Set AutoCompleteMessages = false on the processor — complete messages manually after successful processing
- Implement the ProcessErrorAsync handler to log and alert on errors
- Use MaxConcurrentCalls to control the concurrency level on the processor
- Renew message locks when processing time exceeds the lock duration
- Dead-letter messages that fail validation immediately — do not keep retrying invalid messages
- Monitor the DeliveryCount property to detect frequently failing messages
Summary
Azure Service Bus supports pull-based and push-based message receiving. Pull-based receiving uses ReceiveMessageAsync for on-demand processing. Push-based receiving uses the ServiceBusProcessor for continuous, concurrent message handling. After reading a message in Peek-Lock mode, the receiver settles it by completing, abandoning, dead-lettering, or deferring it. Lock renewal prevents premature lock expiry during long-running processing. Proper settlement and error handling ensure reliable message delivery without data loss.
