【问题标题】:.Wait() causes TaskCanceledException.Wait() 导致 TaskCanceledException
【发布时间】:2016-11-06 15:04:51
【问题描述】:

我有一个发送电子邮件的功能,如下所示:

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);
        }

如您所见,它不是“真正的异步”,而是“延迟执行”,因此我可以调用此方法,并且不会阻塞当前调用线程。

现在,我有时需要一种方法来等待电子邮件发送完毕,然后再继续。所以我这样调用我的 SendMail() 方法:

EmailService.Instance.SendEmail("from@blah.com", "to@blah.com", "Subject", "Body text").Wait();

最后带有 .Wait()。

由于某种原因使用 .Wait() - 试图强制同步执行,导致异常:

System.Threading.Tasks.TaskCanceledException:任务被取消

问题:

1) 为什么会出现此异常?

2) 如何强制同步执行该方法?

谢谢

【问题讨论】:

  • 您遇到了异常,因为任务在“已取消”状态下完成(Wait() 并没有“导致”这种情况,它只是传播了该信息)。
  • 如果您取出 ContinueWith 并使用 try/catch,它的行为是否相同?目前尚不清楚您为什么要在此处使用 ContinueWith,但可能发生的情况是,由于主要任务(来自 Task.Run)成功完成,继续任务被取消。
  • 另外,SendCompleted 事件仅与SendAsync 方法一起引发。同步 Send 方法不会引发事件。是否有理由不使用基于任务的方法SendMailAsync
  • 为什么这个问题被大量否决?它是有效的和好的!

标签: c# async-await task-parallel-library wait


【解决方案1】:

1) 为什么会出现此异常?

您收到异常是因为,

  • 原始任务顺利完成,没有任何错误
  • 您的 continuationTaskContinuationOptions 设置为 TaskContinuationOptions.OnlyOnFaulted
  • 由于原始 Task 的执行没有错误,您会收到 AggregateException: A task was cancelled。 因为继续没有执行并且它被取消了

2) 如何强制同步执行此方法?

你可以强制同步执行,

例如

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

通过注释/取消注释虚拟异常来检查以下程序在原始任务中抛出错误以及原始任务完成时的行为方式。您可以在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);
        }
    }
}

如果您只是在使用 ContinueWith 方法出现任何问题时才记录异常,那么您可以摆脱该 ContinueWith 并放置一个 try catch块在原始任务中捕获任何异常并记录它们。

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);
        }
    });
}

否则,如果您想在原始Task完成后做一些额外的工作,您可以按照以下方式进行。

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
                }
            });
        }
    }
}

【讨论】:

  • 很好的答案。我只是简单地包裹在一个 try-catch 块中。即使我希望有一种方法可以在使用 ContinueWith() 选项时不抛出 CancellationException。
  • 谢谢,您可以在没有 TaskContinuationOptions 的情况下调用 ContinueWith 方法,例如 OnlyOnFaulted 以避免 TaskCanceledException rextester.com/WHB97904
  • @Boriska64,你想知道是否有办法使用 ContinueWith & TaskContinuationOptions.OnlyOnFaulted 而不会得到 TaskCanceledException 我认为这是默认行为,请查看 TaskContinuationOptions Enumeration 了解更多关于 TaskContinuationOptions在这里msdn.microsoft.com/en-us/library/…
  • 非常感谢,我为类似这样的代码奋斗了多久。就像所有电子邮件都已发送一样,但我仍然收到这个奇怪的取消异常。谢谢一百万。
  • @GhasanAl-Sakkaf 我很乐意为您提供帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-21
  • 2020-12-12
相关资源
最近更新 更多