3

I'm trying to create an worker service that polls database every few seconds for emails. Emails are passed to a priorityQueue and from there sent via Rest API to external provider. I would like to also implement some kind of limit on number of threads used by queue.

So here is my ExecuteAsync from BackGroundService:

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {/*Poll email database*/
            var emails = await _emailRepository.GetEmailsToSend();

            foreach (var email in emails) /*Queue downloaded emails*/
                _queue.QueueEmail(email);

            await Task.Delay(_emailOptions.PollTime, stoppingToken);
        }
    }

And here is my BackgroundEmailQueue to which emails are given:

public class BackgroundEmailQueue : IBackgroundEmailQueue
{
    private readonly ILogger<BackgroundEmailQueue> logger;
    private readonly ConcurrentPriorityQueue<EmailToSend> queue;
    private readonly SemaphoreSlim signal;

    private event Action EmailQued;

    public BackgroundEmailQueue(ILogger<BackgroundEmailQueue> logger)
    {
        this.queue = new ConcurrentPriorityQueue<EmailToSend>();
        this.signal = new SemaphoreSlim(5, 5);
        this.EmailQued += OnEmailQued;
        
        this.logger = logger;
    }

    public void QueueEmail(EmailToSend email)
    {
        if (email == null)
            return;

        queue.Add(email);

        EmailQued.Invoke();
    }

    private async void OnEmailQued()
    {
        await signal.WaitAsync();

        var email = queue.Take();

        await Task.Run(async () => { .... /*sending email*/ } );

        signal.Release();
    }

}

This solution works as intended

My problem is:

Was using the event to fire new thread good solution?

Is there a better way to do this?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
DavidWaldo
  • 661
  • 6
  • 24
  • You can use Parallel.ForEach where we will have the total control of number of max async threads. https://stackoverflow.com/questions/9538452/what-does-maxdegreeofparallelism-do – Sowmyadhar Gourishetty Aug 10 '20 at 08:31
  • But when or where do you fire the Parallel.ForEach, service polls the database every few seconds. The queue can have thousands of emails in one moment and none few minutes later. – DavidWaldo Aug 10 '20 at 08:33
  • Have you considered using a `BlockingCollection`? Perhaps Google for `blockingcollection process in parallel`? – mjwills Aug 10 '20 at 08:34
  • If you want code to be reviewed than https://codereview.stackexchange.com/ might be a better place. – JonasH Aug 10 '20 at 08:36
  • To mjwills: well, I dont really have a problem with collection itself. I need an priority queue becouse emails have an priority. Whole point of the code is to solve peaks when thousands of emails are added to database. What my problem is: I dont know how to efficiently start up those threads doing the sending or where to start them up, or when. – DavidWaldo Aug 10 '20 at 08:42
  • What type is the application that runs the `ExecuteAsync` method? I am asking because I am interested in whether there is installed a `SynchronizationContext` or not. – Theodor Zoulias Aug 10 '20 at 11:23
  • Theodor: Its a worker service – DavidWaldo Aug 10 '20 at 11:27
  • How do you handle exceptions while sending an email? Do you have any mechanism in place that informs the database whether an email was sent successfully or not? – Theodor Zoulias Aug 10 '20 at 13:00
  • Yes, there is an logging and error catching. Email will be just sent on another try. – DavidWaldo Aug 10 '20 at 13:38
  • So you do communicate with the database **after** sending each email. Then why don't you do the same **before** sending each email? In other words why don't you use your database as a priority queue, and you complicate things by loading all emails in an in-memory queue? Your current setup seems to me susceptible to malfunctions like sending an email twice or never. – Theodor Zoulias Aug 10 '20 at 15:45
  • That would mean getting only one email for each request. Already tried that, was very slow. When you have tens of thousands emails you need every boost you can get. – DavidWaldo Aug 11 '20 at 05:21

0 Answers0