2

Below is the function where I am trying to execute a function in the background and then carry on without waiting for a result from it.

When debugging the task itself is executed but the actual function within it does not. The rest of the code then carries on like normal.

What could be the issue as there is no error produced after that to indicate otherwise?

This is on a page load.

public ActionResult ExceptionReport(int? id)
{
    var ExceptionList = db.Invoices.Where(m => m.ExceptionFlag == true && m.GlobalInvoiceID == id);

    if (ExceptionList.Count() == 0)
    {

        globalInvoice.Status = "Exception Verification";
        db.Entry(globalInvoice).State = EntityState.Modified;
        db.SaveChanges();

        Task.Run(() => ExceptionFinalTests(globalInvoice)); //Function To run in the background



        TempData["warning"] = "Verifying all exceptions fixed. A notification will be sent when the verifications are complete.";
        return RedirectToAction("Index", "GlobalInvoices");
    }

    return View(ExceptionList);
}


   private void ExceptionFinalTests(GlobalInvoice globalInvoice)
    {
        RunTests(globalInvoice, true);

        decimal TotalPaymentAmount = db.Invoices.Where(m => m.GlobalInvoiceID == globalInvoice.Id).Sum(m => m.Invoice_Amount) ?? 0;
        }

        GlobalInvoicesController globalInvoicesController = new GlobalInvoicesController();

        var ApproverList = globalInvoicesController.GetUserEmailsInRole(globalInvoice, "Reviewer");

        globalInvoicesController.Dispose();

        var exceptionExistCompulsoryTest = db.Invoices.Where(m => m.ExceptionFlag == true && m.GlobalInvoiceID == globalInvoice.Id);

        if (exceptionExistCompulsoryTest.Count() > 0)
        {
            try
            {
                string baseUrl = ConfigurationManager.AppSettings["site"];

                EmailExtension emailExtension = new EmailExtension();

                foreach (var approver in ApproverList)
                {
                    string approvalLink = baseUrl + "/Invoices/ExceptionReport/" + globalInvoice.Id;

                    StringBuilder mailbody = new StringBuilder();
                    mailbody.AppendFormat("Hi<br/>");
                    mailbody.AppendFormat("There are " + exceptionExistCompulsoryTest.Count() + " exceptions for invoice #" + globalInvoice.Id + "that need attention before proceeding. - <a href='" + approvalLink + "'>Click Here</a> <br/><br/>");

                    mailbody.AppendFormat("Exception Count: {0}<br/>", exceptionExistCompulsoryTest.Count());
                    mailbody.AppendFormat("Invoice Amount: {0}<br/>", TotalPaymentAmount.ToString("C"));
                    mailbody.AppendFormat("Reviewed By: {0} <br/>", "");
                    mailbody.AppendFormat("Approved By: {0} <br/>", "");

                    EmailVM emailVM = new EmailVM()
                    {
                        Subject = "Invoice - #" + globalInvoice.Id,
                        EmailAddress = approver,
                        Message = mailbody.ToString()
                    };

                    emailExtension.SendEmail(emailVM);
                }
            }
            catch (Exception ex)
            {
                LogWriter.WriteLog(ex.Message);
                LogWriter.WriteLog(ex.StackTrace);
            }
        }
    }


    private void RunTests(GlobalInvoice globalInvoice, bool retestFlag = false)
    {

        List<Invoice> invoices;
        var vendorTests = globalInvoice.Vendor.VendorTests;
        string[] testsToRun = vendorTests.Split(',');

        if (retestFlag == true)
        {
            if (globalInvoice.Vendor.VendorHasHierarchy == true)
            {
                testsToRun = new string[] { "Account Number", "Hierarchy" };
            }
            else
            {
                testsToRun = new string[] { "Account Number" };
            }
        }

            using (var context = new MyContext())
            {
                invoices = context.Invoices.Where(m => m.GlobalInvoiceID == globalInvoiceToTestID).ToList();
            }


        foreach (var test in testsToRun)
        {
            if (test == "Account Number")
            {
                LogWriter.WriteLog("Starting Account Number Check : Invoice Batch ID - " + globalInvoice.Id);
                AccountNumberCheck(invoices, globalInvoice.VendorID);
                LogWriter.WriteLog("Account Number Check Complete : Invoice Batch ID - " + globalInvoice.Id);
            }
            if (test == "Hierarchy")
            {
                LogWriter.WriteLog("Starting Hierarchy Check : Invoice Batch ID - " + globalInvoice.Id);
                BillingHierarchyCheck(invoices);
                LogWriter.WriteLog("Hierarchy Check Complete : Invoice Batch ID - " + globalInvoice.Id);
            }
        }
    }
user2806570
  • 821
  • 2
  • 12
  • 25
  • 1
    What does `ExceptionFinalTests` do? You are modifying the reference to `globalInvoice` before that method has a chance to run – Camilo Terevinto Jun 06 '18 at 11:15
  • If you want the action to execute tasks, consider using public async Action ExceptionReport(int? id). I would also check id for value and you need to use id.Value in your LINQ statement. If you have tasks that need to run elsewhere, I'd put them in the Global.asax and consider static methods for these so you can check on progress later and if necessary have cancellation tokens –  Jun 06 '18 at 11:22
  • I've altered the code to make the changes to globalInvoice first and then run the task. I've also added the other related function above where you can see what I am trying to do. ExceptionFinalTests will then run the tests on the "Invoices" tied to the globalInvoice and based on the exceptions send out an email to whoever needed. – user2806570 Jun 06 '18 at 11:27
  • Debugging async operations is always kinda tricky, because they run asynchronous. – Nikolaus Jun 06 '18 at 11:28
  • Check the output/logs of the application. I am 99% sure you are actually getting an `ObjectDisposedException` on one of the `db` references since that's most likely disposed as soon as you call `return RedirectToAction("Index", "GlobalInvoices");`. I am guessing though, as you didn't post where/how is `db` declared – Camilo Terevinto Jun 06 '18 at 11:30
  • why do you wan to wrap ExceptionFinalTests(globalInvoice) in `Task.Run`? Your whole code is written in a manner to be synchronous. Wrapping in `Task.Run` you are will give you these odd behaviour. I would suggest if you can write the from entry point to be asynchronous code. – user1672994 Jun 06 '18 at 11:31
  • From the logs the error that is being shown is "The operation cannot be completed because the DbContext has been disposed. " – user2806570 Jun 06 '18 at 11:39
  • Probably because your `db` context object has been disposed when the controller action exited, because basically the HTTP request has ended and returned a response to the client. A web application generally assumes that when it's returned a response to the client, its job is done. if you're going to execute potentially long-running (more than a few seconds) and/or asynchronous tasks, especially potentially slow or unreliable ones like sending emails, you're better shifting this into a separate background Windows Service, or plugging in a library like Quartz or Hangfire to schedule tasks – ADyson Jun 06 '18 at 12:54
  • So I managed to fix the error of the disposed DbContext with using(Context). When I debug line by line the task executes correctly but doesn't run at all when I run the program normally without any breaks. Not sure if I need a delay for the Task before it begins or its something else? – user2806570 Jun 06 '18 at 13:42
  • [https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx](https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx) – Damien_The_Unbeliever Jun 06 '18 at 13:52
  • Managed to solve the issue by adding a Thread.Sleep(3000). – user2806570 Jun 06 '18 at 14:01
  • @user2806570 yuk...probably slower than just making the whole thing synchronous...read my comment and Damien's link to understand why yours is not a great design. – ADyson Jun 06 '18 at 14:06
  • 2
    Do you not see that by adding the `Sleep` it's *not* now fire-and-forget and so you may as well just run the code inline and throw away the `Task.Run`? – Damien_The_Unbeliever Jun 06 '18 at 14:11
  • The function ExceptionFinalTests runs for around 5 minutes or longer which is why I can't really have someone wait on a page for that long. Running a separate windows service is not really an option which is why I am not doing it there. – user2806570 Jun 07 '18 at 10:52
  • @ADyson The Hangfire option does look like a good option for me to improve on the current solution I have. – user2806570 Jun 07 '18 at 10:57
  • Possible duplicate of [Fire and forget async method in asp.net mvc](https://stackoverflow.com/questions/18502745/fire-and-forget-async-method-in-asp-net-mvc) – Liam Jul 27 '18 at 10:09

0 Answers0