3

In my MVC application, I am having a functionality of sending message to the respective owners/dealers, by clients. The message model is as below:

public class Messages
{
   public string MessageFrom { get; set; }
   public string MessageUserEmailID { get; set; }
   public string MessageUserContactNum { get; set; }
   public string Message { get; set; }
   public int UserID { get; set; }//Admin user id
   public string propertyName { get; set; }
}

and below is my Controller method which will be called through AJAX where

  • I store the message in DB
  • Send the admin a notification through email
  • return a Json response back to user.

HomeController

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SendMessage([Bind(Prefix = "messageModel")]Messages model)
{
     private SendNotification notify = new SendNotification();
     if (ModelState.IsValid)
     {
          using (db)
          {
             tblMessage messageModel = new tblMessage();
             //....
             //....
             //....
             //fill the messageModel with the model values
             db.tblMessages.Add(messageModel);
             db.SaveChanges();
             string toAddress = db.tblUsers.Where(u => u.UserId == model.UserID).Select(x => x.UserEmailID).FirstOrDefault();
             if (!string.IsNullOrEmpty(toAddress))
             {
                  notify.NotifyUser(toAddress, model.MessageUserContactNum, model.propertyName, model.Message, model.MessageFrom, model.MessageUserEmailID, "Kemmale Engineers' Notification - You have a message");
             }
             return Json(new { result = true, message = "Message sent! Thank you! We will get back to you soon!" }, JsonRequestBehavior.AllowGet);
         }
    }
    return Json(new { result = false, message = "Server issue! Please try again later" }, JsonRequestBehavior.AllowGet);
}

SendNotification class contains NotifyUser method which is as below:

public void NotifyUser(string toAddress, string userPhoneNum, string propertyName, string userMessage, string userName, string userContactEmail, string subject)
{
    using (MailMessage mail = new MailMessage())
    {
        string Password = WebConfigurationManager.AppSettings["ApplicationEmailPassword"];
        var securePassword = new SecureString();
        Array.ForEach(Password.ToArray(), securePassword.AppendChar);
        securePassword.MakeReadOnly();
        mail.To.Add(toAddress);
        mail.From = new MailAddress(WebConfigurationManager.AppSettings["ApplicationEmailID"]);
        mail.Subject = subject;
        mail.Body = Convert.ToString(ConstructBody(userMessage, propertyName, userPhoneNum, userContactEmail, userName));
        mail.IsBodyHtml = true;
        mail.Priority = MailPriority.High;
        SmtpClient smtp = new SmtpClient();
        smtp.Host = "smtp.gmail.com";
        smtp.Port = 587;
        smtp.UseDefaultCredentials = false;
        smtp.EnableSsl = true;
        smtp.Credentials = new NetworkCredential(Convert.ToString(mail.From), securePassword);
        smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
        smtp.Send(mail);
    }
}

As you can see that the Notify has return type void. Now how can I return Json message to the user without waiting for Notify method to get executed, since it is very obsolete to make user wait for internal process to complete which is irrelevant to the user? I am thinking of Asynchronous Task but not sure how can I implement it here.

Guruprasad J Rao
  • 29,410
  • 14
  • 101
  • 200
  • Task.Run(() => notify.NotifyUser(...)); – Gusman Jan 06 '16 at 17:56
  • @Gusman.. Could you please elaborate? – Guruprasad J Rao Jan 06 '16 at 17:58
  • Er... no, there's nothing to elaborate, Just surround the call to notify.NotifyUser with a lambda and pass it to Task.Run, it will run in another thread and the response will be immediate without waiting to the mail sending – Gusman Jan 06 '16 at 18:03
  • @Gusman. Thank you.. Implemented it but since am using `.net framework 4` I had to use `Task.Factory.StartNew(() => {NotifyUser();});`. You can please add it as answer, so that I can accept it.. – Guruprasad J Rao Jan 06 '16 at 18:42

1 Answers1

3

If you want to execute the function and not to wait it to finish, then you must run it in another thread.

The simplest approach is to use the Task functionality.

In FW4.5 or higher you can use

Task.Run(ActionToExecute)

Or if you're using FW4.0 can use

Task.Factory.StartNew(ActionToExecute).

Else, you can do it the old-fashioned way using the thread pool:

ThreadPool.EnqueueUserWorkItem(ActionToExecute, null);

But in this case the action must have an object parameter (even if it's ignored).

Gusman
  • 14,905
  • 2
  • 34
  • 50
  • Also could you please add points on performance issues if any? – Guruprasad J Rao Jan 06 '16 at 18:47
  • There are none, the performance will be exactly the same as executing the function in the same thread. – Gusman Jan 06 '16 at 18:48
  • 1
    Using `Task.Run` in ASP.NET is a terrible idea. Saying this will have no performance impact is also incorrect, as you're basically doubling the threads needed to handle a single request. – Yuval Itzchakov Jan 06 '16 at 18:48
  • 1
    @YuvalItzchakov Why? Can you elaborate? I have used for ages worker threads on ASP .net without any trouble – Gusman Jan 06 '16 at 18:49
  • 1
    The thread pool will handle how many worker threads will be running, so there's no problem at all and no performance impact, you will not double the thread count, if you hit the limit of the thread pool it will queue the task until any thread is freed. – Gusman Jan 06 '16 at 18:51
  • For one, if a pool recycle is enforced, any work being done using `Task.Run` isn't registered with the ASP.NET runtime. This will simply cause any ongoing work to abort without a chance to complete. – Yuval Itzchakov Jan 06 '16 at 18:51
  • 1
    Any long-running task will be cancelled even if it's a normal call if you force the app pool to be recycled... – Gusman Jan 06 '16 at 18:52
  • *"The thread pool will handle how many worker threads will be running, so there's no problem at all and no performance impact"* Really? And those threads, do they not consume memory? Do they not require CPU time and cause context switches? Giving a general statement such as that is usually wrong. – Yuval Itzchakov Jan 06 '16 at 18:53
  • If you register work with the runtime, you will have sufficient time to complete any ongoing operation. – Yuval Itzchakov Jan 06 '16 at 18:54
  • 1
    really? are you serious? If we go that way, using the thread pool will increment the performance in any computer with more than a core, so, you will loose some ram Kb but will gain speed.. – Gusman Jan 06 '16 at 18:54
  • *"using the thread pool will increment the performance in any computer with more than a core"* Giving such general statements is usually what makes developers assume "more threads is better", and that is an absolute lie. It is always about the usecase. Spinning up new threads may also end up decreasing performance – Yuval Itzchakov Jan 06 '16 at 18:56
  • 1
    I didn't say more threads are better. As I said, ThreadPool will queue tasks if there are no free threads. – Gusman Jan 06 '16 at 18:57
  • So guys @Gusman and Yuval is it safe to use task.run? – Benyamin Limanto Jun 14 '22 at 05:01
  • 1
    @BenyaminLimanto Yes, it is safe for something like sending an email that is a short-lived background task. If you want to do more complex or longer tasks then the correct approach would be using a singleton service that implements IRegisteredObject and handles all the background tasks. – Gusman Jun 14 '22 at 10:16
  • @Gusman is that available in .NET Core? I'm quite new to .NET Web Development tbh. I already look into the docs https://learn.microsoft.com/en-us/dotnet/api/system.web.hosting.iregisteredobject?view=netframework-4.8&viewFallbackFrom=net-6.0, seems not possible in .NET Core – Benyamin Limanto Jun 14 '22 at 14:54
  • 1
    @BenyaminLimanto There is no need to use IRegisteredObject in .net core. In .net core you can register a function to be notified when the app is being stopped using the `ApplicationStopping` cancellation token from the `IHostApplicationLifetime` passed to the `Configure` method of your `Startup` class, that gives you the opportunity to destroy any one of your services that needs to do any cleanup. More info here: https://stackoverflow.com/questions/36702948/replacement-for-iregisteredobject-in-asp-net-5 – Gusman Jun 14 '22 at 18:37