Search code examples
.net-coreazureservicebusasp.net-core-hosted-services

Start Service Bus Client from BackgroundService


I have a ServiceBusClient class that creates a QueueClient which is used to listen for messages on a bus. I have looked at the following articles to set this up:

Background tasks (Microsoft)

Hosted services (Microsoft)

Async and Await

My ServiceBusClient class that handles the QueueClient looks like this:

public class ServiceBusClient : IServiceBusClient
{
    public ServiceBusClient(IEventService eventService, ServiceBusClientOptions options)
    {
        ...
        queueClient = new QueueClient(options.ConnectionString, options.QueueName);
    }

    public void Run()
    {
        RegisterOnMessageHandler();
    }

    private void RegisterOnMessageHandler()
    {
        ...
        queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
    }

    private async Task ProcessMessagesAsync(Message message, CancellationToken token)
    {
        var eventMessage = EventMessage.FromMessage(message);

        await eventService.Write(eventMessage);

        if (!token.IsCancellationRequested)
        {
            await queueClient.CompleteAsync(message.SystemProperties.LockToken);
        }
    }

    private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs)
    {
        // log errors
        ...

        return Task.CompletedTask;
    }  
}

I was hoping to launch from an IHostedService or even by extending the BackgroundService. In the examples I find, work is constantly being executed in a while loop which does not fit my scenario since I am only trying to run a single command.

So I created a super simple implementation like this:

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
    serviceBusClient.Run();

    while (!cancellationToken.IsCancellationRequested)
    {
        // empty loop to keep running for lifetime of pod       
    }
}

If removing the async I obviously need to return something. I tried Task.CompletedTask but that required me to change the return type to Task<Task>.

If I have the async in place, I will need to await something, but I am not sure what.

This does not feel right. I would assume I would need to change something in the ServiceBusClient, but I am unsure what, since the ProcessMessagesAsync is async and does the heavy lifting in the background from my understanding.

All I want is for my web app to start listening for messages until it dies. How can I do that?


Solution

  • I gave up on using BackgroundService and implemented IHostedService instead.

        public class MessageListenerService : IHostedService
        {
            private readonly IServiceBusClient client;
            private readonly ITelemetryClient applicationInsights;
    
            public MessageListenerService(IServiceProvider serviceProvider)
            {
                client = serviceProvider.GetService<IServiceBusClient>();
                applicationInsights = serviceProvider.GetService<ITelemetryClient>();
            }
    
            public Task StartAsync(CancellationToken cancellationToken)
            {
                applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is starting"));
    
                client.Run();
    
                return Task.CompletedTask;
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is stopping"));
                return client.Stop();
            }
        }
    

    If you find issues with this code please let me know in the comments and I'll update as appropriate.

    In the end we created a console app for it anyway.