Which TPL suits best for this scenario in .NET API ? Task/Threads

jonrexh

I have this method that increases the views of a video when it's watched. If the video of the person who published it reaches 200 views, I send an congrats email to him, but if the email sending DELAYS, I don't want the user to wait for that delay to watch the video. I need to make Video loading not depend on email sending. I have used Task.Run and Task.Factory.StartNew and inside it Parallell I don't have experience with tasks/threads so which is the best way to implement it.

public async Task<ServiceResponse<ViewedVideoDto>> AddVideoView(int candidateVideoId)
        {
            var video = await _dbcontext.CandidateFiles.Include(x=>x.Candidate).ThenInclude(x=>x.User).FirstOrDefaultAsync(x => x.Id == candidateVideoId);
            if (video is null)
            {
                return new ServiceResponse<ViewedVideoDto>().NotFound(nameof(Resource.VideoEmpty), Resource.VideoEmpty);
            }
            video.VideoViews += 1;
            _dbcontext.Update(video);
            await _dbcontext.SaveChangesAsync();
            if (video.VideoViews == 200)
            {
                //Method 1 
                await Task.Run(() =>
                   _candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
                       video.Candidate.User.FullName));

                //Method 2
                   Task.Factory.StartNew(() => Parallel.Invoke(() =>
                  {
                      _candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
                          video.Candidate.User.FullName);
                  }));
                  
            }
            return new ServiceResponse<ViewedVideoDto>()
            {
                Data = new ViewedVideoDto()
                {
                    VideoId = video.Id
                }
            };
            

        }

 public async Task Achieved200VideoViews(string receiverEmail, string fullName)
    {
        var candidateTemplate = EmailTemplate.Return(EmailTemplateConst.CanEmailTempFolder, EmailTemplateConst.CanAchieved200VideoViews,fullName);
            
        var innoEmail = new InnoEmail()
        {
            Email = receiverEmail,
            Subject = "Touchpoint after 200 views",
            HtmlMessage = candidateTemplate 
        };
        await _emailService.SendAsync(innoEmail);
public async Task<bool> SendAsync(Email email)
        {
            try
            {
                var apiKey = _configuration.GetSection("SENDGRID_API_KEY").GetSection("SecretKey").Value;
                var Email = _configuration.GetSection("SENDGRID_API_KEY").GetSection("Email").Value;

                var client = new SendGridClient(apiKey);
                var msg = new SendGridMessage()
                {
                    From = new EmailAddress(Email, "test"),
                    Subject = email.Subject,
                    PlainTextContent = null,
                    HtmlContent = email.HtmlMessage
                };
                msg.AddTo(new EmailAddress(email.Email));

                var response = await client.SendEmailAsync(msg).ConfigureAwait(false);

                response.StatusCode.ToString();
                return true;
            }
            catch (System.Exception e)
            {
                return false;
            }
        }
Guru Stron

The Method 1 (await Task.Run(() =>) will not achieve the declared goal cause you are awaiting the result which basically will suspend the AddVideoView execution until the Achieved200VideoViews finishes. If you want fire-and-forget behaviour - remove the await.

Based on Achieved200VideoViews implementation, basically it just sends mail - I would say that it does not matter which one is better cause both fire-and-forget approaches are not good enough, cause they lack observability (for example email sending can fail for multiple reasons or app will be restarted during the process).

I would suggest and approach similar to transactional outbox pattern - add either a flag to video table or separate table which will store info about notification about 200 views send (something like a video.ToSend200ViewNotification) and update that in AddVideoView and then process that table in some background job handler for example using hosted services or Hangouts or Quartz (or add flag Is200ViewNotificationSend and process all videos with views >= 200 and Is200ViewNotificationSend set to false).

If you are ok with limitations of fire-and-forget approach for synchronous operations Task.Run should be preferable (without await). Task.Factory.StartNew, as written in the docs TaskFactory.StartNew is more suitable for more complicated scenarios (also it is not task-aware and in "ordinary" cases will not handle methods returning Task's correctly):

Starting with the .NET Framework 4.5, the Task.Run method is the recommended way to launch a compute-bound task. Use the StartNew method only when you require fine-grained control for a long-running, compute-bound task. This includes scenarios in which you want to control the following:

  • Task creation options. Tasks created by the Task.Run method by default are created with the TaskCreationOptions.DenyChildAttach option. To override this behavior, or to provide other TaskCreationOptions options, call a StartNew overload.
  • Parameter passing. The overloads of the Task.Run method do not allow you to pass a parameter to the task delegate. Overloads of the StartNew method do.
  • The task scheduler. The overloads of the Task.Run method use the default task scheduler. To control the task scheduler, call a StartNew overload with a scheduler parameter. For more information, see TaskScheduler.

But since your Achieved200VideoViews method already returns Task and assuming it is "truly async" then just skipping await on the invokation should be fine to achieve fire-and-forget functionality (and I would say it is preferable cause it should be less resource-consuming):

_ = _candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
                       video.Candidate.User.FullName);

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Which graph traversal algo suits in this scenario

Which design is the best for this scenario?

Which is the best scenario for use IdentityServer?

Which Tensorflow object detection model suits satellite images best?

Which of Ruby's concurrency devices would be best suited for this scenario?

Which strategy suits best a user with a scientific background like me? (Linux Desktop, Dual Boot, Virtualization)/ (Fedora? Arch? Win?)

Which exception would best fit with a "you shouldn't be running this method in this context" scenario?

C# Azure CosmosDb and Mongo - how to know if Find is hitting an index, and which are the best indexing recommendations for this scenario?

a scenario which depict vsftpd

cosmosdb sql api vs mongodb api which one to use for my scenario.

Which supervised machine learning classification method suits for randomly spread classes?

Which data structure in C++ suits to implement webbrowser history?

Which one suits better this case. zipWith or .map?

Best way to write scenario in gherkins

What is the best database structure In this scenario?

Which rxSwift operator to use in this scenario?

Which is a better Firestore schema scenario?

Figuring which thread is working on which scenario

Calling a Java API from .NET - best approach

.Net Core Rest API Best practise

Best way to post to a .Net Core Web API

which is the best and fast command parameters in oledb in vb.net

which is the best API to read large sized excel files in Java?

Which life cycle hook is best practise to dispatch an API fetch request with?

which is the best API gateway for micro services using spring?

What is the best way to check which object is returned from an API in typescript?

which is best api in java for data migration between db to db

Is there a better way than Thread.Sleep() in ASP.NET API? See scenario below

mysql: what is the best way to index multicolum in this scenario