8

I am trying to implement a background service, QueuedBackground service with Mediator.

So far I am able to implement the queues but I am unable to execute Mediator.

Interface

public interface IBackgroundTaskQueueService
    {
        void QueueBackgroundWorkItem(object workItem, CancellationToken token);

        Task<object> DequeueAsync(
            CancellationToken cancellationToken);
    }

Implementation

public class BackgroundTaskQueueService : IBackgroundTaskQueueService
    {
        private readonly ConcurrentQueue<(object,CancellationToken)> _workItems =
            new ConcurrentQueue<(object,CancellationToken)>();

        private SemaphoreSlim _signal = new SemaphoreSlim(0);

        public void QueueBackgroundWorkItem(object workItem, CancellationToken token)
        {
            if (workItem == null)
            {
                throw new ArgumentNullException(nameof(workItem));
            }

            _workItems.Enqueue((workItem,token));
            _signal.Release();
        }

        public async Task<object> DequeueAsync( CancellationToken cancellationToken)
        {
            await _signal.WaitAsync(cancellationToken);
            _workItems.TryDequeue(out var workItem);
            return workItem.Item1;
        }
    }

Background Service

public class QueuedHostedService : BackgroundService
    {
   
        private readonly ILogger _logger;
        private readonly IMediator _mediator;
        public QueuedHostedService(IBackgroundTaskQueueService taskQueue, ILoggerFactory loggerFactory, IMediator mediator)
        {
            TaskQueue = taskQueue;
            _mediator = mediator;
            _logger = loggerFactory.CreateLogger<QueuedHostedService>();
        }

        public IBackgroundTaskQueueService TaskQueue { get; }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (false == stoppingToken.IsCancellationRequested)
            {
                try
                {
                    var workItem = await TaskQueue.DequeueAsync(stoppingToken);
                    await _mediator.Send(workItem, stoppingToken);
                    // await _mediator.Send(new UpdateProductCostByMaterialRequestModel()
                    // {
                    //     Id = 1
                    // }, stoppingToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Error occurred executing Work item.");
                }
            }
        }
    }

Usage

_queueService.QueueBackgroundWorkItem(new UpdateProductCostByMaterialRequestModel()
            {
                Id = request.ProductId
            }, CancellationToken.None);

Now with the above code I am able to receive the class object but when I pass it in the Mediator I get InvalidOperation Handler not registered.

I am confused.

supernerd
  • 379
  • 4
  • 13

1 Answers1

14

Okay I found the issue

Instead of passing it from the Constructor I had to use the ServiceFactory Interface

My Solution BackgroundService is a Singleton. You cannot inject a Scoped into a Singleton.

public class QueuedHostedService : BackgroundService
    {
   
        private readonly ILogger _logger;
        private readonly IServiceScopeFactory _serviceScopeFactory;

        public QueuedHostedService(IBackgroundTaskQueueService taskQueue, ILoggerFactory loggerFactory, IServiceScopeFactory serviceScopeFactory)
        {
            TaskQueue = taskQueue;
            _serviceScopeFactory = serviceScopeFactory;
            _logger = loggerFactory.CreateLogger<QueuedHostedService>();
        }

        public IBackgroundTaskQueueService TaskQueue { get; }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            using var scope = _serviceScopeFactory.CreateScope();

            var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
            while (false == stoppingToken.IsCancellationRequested)
            {
                try
                {
                    
                    var workItem = await TaskQueue.DequeueAsync(stoppingToken);
                    if (workItem is IRequest<object> item)
                    {
                        await mediator.Send(workItem, stoppingToken);
                    }
                    // await _mediator.Send(new UpdateProductCostByMaterialRequestModel()
                    // {
                    //     Id = 1
                    // }, stoppingToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Error occurred executing Work item.");
                }
            }
        }
    }

supernerd
  • 379
  • 4
  • 13
  • Thanks, this is working. Can anybody please explain me why this works? Getting a service with `GetRequiredService` vs getting it via constructor, what is the difference? – Venkatesh Dharavath Jan 25 '22 at 07:57
  • 1
    @VenkateshDharavath I have no idea why it works, it just works. – supernerd Jan 25 '22 at 19:38
  • 4
    BackgroundService is a Singleton. You cannot inject a Scoped into a Singleton. – leotuna Jan 28 '22 at 01:16
  • you tought it is work. Absolutely wrong. ExecuteAsyc work for one time and only one time. your CreateScope() can not stay scoped forever first. Some context (forexample IDbContext) can not stay fro ever scope. – Nuri YILMAZ Jun 20 '23 at 23:35