Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Wait() causes TaskCanceledException

I have a function that sends out an email, like so:

public async Task SendEmail(string from, string to, string subject, string body, bool isBodyHtml = false)
        {
            await Task.Run(() =>
            {
                using (SmtpClient smtp = new SmtpClient(host, port))
                {
                    smtp.Credentials = new NetworkCredential(userName, password);
                    smtp.EnableSsl = true;
                    smtp.SendCompleted += SmtpOnSendCompleted;
                    MailMessage message = new MailMessage(from, to, subject, body);
                    message.IsBodyHtml = isBodyHtml;
                    smtp.Send(message);
                }
            }).ContinueWith(task =>
            {
                LoggingService.Instance.BusinessLogger.Error(task.Exception.Flatten().InnerException);

            }, TaskContinuationOptions.OnlyOnFaulted);
        }

As you can see it is not a "true async", but rather a "deffered execution", so that I can call this method and it wouldn't block the current calling thread.

Now, I sometimes need a way to wait for the email to be sent, before proceeding. So I call my SendMail() method like so:

EmailService.Instance.SendEmail("[email protected]", "[email protected]", "Subject", "Body text").Wait();

with a .Wait() at the end.

For some reason using .Wait() - trying to force synchronous execution, cause exception:

System.Threading.Tasks.TaskCanceledException: A task was canceled

Questions:

1) Why am I getting this exception?

2) How do I force synchronous execution of this method?

Thanks

like image 355
CloudDev Avatar asked Nov 06 '16 05:11

CloudDev


1 Answers

1) Why am I getting this exception?

You are getting the exception because,

  • The original Task completed successfully without any faults
  • You're having a continuation with TaskContinuationOptions set as TaskContinuationOptions.OnlyOnFaulted
  • As there were no faults in the original Task's execution, you get a AggregateException: A task was canceled. because the continuation didn't execute and it was canceled

2) How do I force synchronous execution of this method?

You can force synchronous execution by,

  • Using Task.Wait Method (TimeSpan)
  • Using Task.RunSynchronously Method ()

e.g.

var task = new Task(() => { ... });
task.RunSynchronously();

Check how the below program behaves when you throw an error in the original Task and when the original Task completes without any faults by commenting/uncommenting the dummy exception. You can execute the below program at http://rextester.com/

using System;
using System.Threading.Tasks;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                DoSomething().Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex.InnerException.Message);
            }

            Console.WriteLine("DoSomething completed");
        }

        public static async Task DoSomething()
        {
            await Task.Factory.StartNew(() =>
            {
                System.Threading.Thread.Sleep(1000);
                Console.WriteLine("Doing Something");
                // throw new Exception("Something wen't wrong");
            }).ContinueWith(task =>
            {
                Console.WriteLine(task.Exception.InnerException.Message);
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

If you're only logging the exception when anything goes wrong using the ContinueWith method, then you can get rid of that ContinueWith and put a try catch block inside the original Task to catch any exceptions and log them.

static void Main(string[] args)
{
    DoSomething().Wait();
    Console.WriteLine("DoSomething completed");
    Console.ReadKey();
}

public static async Task DoSomething()
{
    await Task.Factory.StartNew(() =>
    {
        try
        {
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Doing Something");
            throw new Exception("Something wen't wrong");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    });
}

Otherwise, if you want to do some additional work after original Task completes you can do it as follows.

namespace SO
{
    using System;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            DoSomething().Wait();
            Console.WriteLine("DoSomething completed");
            Console.ReadKey();
        }

        public static async Task DoSomething()
        {
            await Task.Factory.StartNew(() =>
            {
                System.Threading.Thread.Sleep(1000);
                Console.WriteLine("Doing Something");
                // throw new Exception("Something wen't wrong");
            }).ContinueWith(task =>
            {
                if (task.Status == TaskStatus.Faulted)
                {
                    // log exception
                    Console.WriteLine(task.Exception.InnerException.Message);
                }
                else if (task.Status == TaskStatus.RanToCompletion)
                {
                    // do continuation work here
                }
            });
        }
    }
}
like image 146
Abdul Mateen Mohammed Avatar answered Oct 30 '22 13:10

Abdul Mateen Mohammed