SmtpClient.SendMailAsync抛出一个特定的异常时,导致死锁 [英] SmtpClient.SendMailAsync causes deadlock when throwing a specific exception

查看:1056
本文介绍了SmtpClient.SendMailAsync抛出一个特定的异常时,导致死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图设置电子邮件确认为ASP.NET MVC5网站的基础上,从VS2013项目模板的例子的AccountController。我已经实现了 IIdentityMessageService 使用 SmtpClient ,试图保持它尽可能简单:

I'm trying to setup email confirmation for an ASP.NET MVC5 website, based on the example AccountController from the VS2013 project template. I've implemented the IIdentityMessageService using SmtpClient, trying to keep it as simple as possible:

public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        using(var client = new SmtpClient())
        {
            var mailMessage = new MailMessage("some.guy@company.com", message.Destination, message.Subject, message.Body);
            await client.SendMailAsync(mailMessage);
        }
    }
}

控制器code正在调用它是直接从模板(提取到,因为我想,以排除其他可能的原因单独行动):

The controller code that is calling it is straight from the template (extracted into a separate action since I wanted to exclude other possible causes):

public async Task<ActionResult> TestAsyncEmail()
{
    Guid userId = User.Identity.GetUserId();

    string code = await UserManager.GenerateEmailConfirmationTokenAsync(userId);
    var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = userId, code = code }, protocol: Request.Url.Scheme);
    await UserManager.SendEmailAsync(userId, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

    return View();
}

但是我得到当邮件无法发送,但只在一个特定的情况下,当主持人是某种奇怪的无法访问行为。示例配置:

However I'm getting odd behavior when the mail fails to send, but only in one specific instance, when the host is somehow unreachable. Example config:

<system.net>
    <mailSettings>
        <smtp deliveryMethod="Network">
            <network host="unreachablehost" defaultCredentials="true" port="25" />
        </smtp>
    </mailSettings>
</system.net>

在这种情况下,请求出现死​​锁,从不返回任何东西到客户端。如果邮件没有发出任何其他原因(如主机主动拒绝连接)的异常通常处理,我得到一个YSOD。

In that case, the request appears to deadlock, never returning anything to the client. If the mail fails to send for any other reason (e.g. host actively refuses connection) the exception is handled normally and I get a YSOD.

纵观Windows事件日志,似乎是一个出现InvalidOperationException 围绕同一时间内抛出,消息异步模块或处理程序完成,而异步操作是仍悬而未决。;我得到了同样的消息在YSOD如果我试图赶上 SmtpException 控制器,并在返回的ViewResult catch块。所以我想在等待 -ed操作失败,在这两种情况下才能完成。

Looking at the Windows event logs, it seems that an InvalidOperationException is thrown around the same timeframe, with the message "An asynchronous module or handler completed while an asynchronous operation was still pending."; I get that same message in a YSOD if I try to catch the SmtpException in the controller and return a ViewResult in the catch block. So I figure the await-ed operation fails to complete in either case.

据我所知道的,我下面所有的异步/的await在其他职位概述了SO最佳实践(例如<一个href=\"http://stackoverflow.com/questions/10343632/httpclient-getasync-never-returns-when-using-await-async\">HttpClient.GetAsync(...)当使用的await /异步),主要是使用异步/等待了一路永不再来。我已经使用 ConfigureAwait(假)也试过,没有变化。因为只有当一个特定的异常被抛出了code死锁,我想一般模式是大多数情况下是正确的事,但内部的动作,使得它在这种情况下不正确;但因为我是pretty新并发编程,我有一种感觉,我可能是错的。

As far as I can tell, I am following all the async/await best practices as outlined in other posts on SO (e.g. HttpClient.GetAsync(...) never returns when using await/async), mainly "using async/await all the way up". I've also tried using ConfigureAwait(false), with no change. Since the code deadlocks only if a specific exception is thrown, I figure the general pattern is correct for most cases, but something is happening internally that makes it incorrect in that case; but since I'm pretty new to concurrent programming, I've a feeling I could be wrong.

有什么我做错了吗?我可以总是使用同步调用(即 SmtpClient.Send())的SendAsync方法,但感觉这应该工作原样。

Is there something I'm doing wrong ? I can always use a synchronous call (ie. SmtpClient.Send()) in the SendAsync method, but it feels like this should work as is.

推荐答案

试试这个实现,只需要使用 client.SendMailExAsync 而不是 client.SendMailAsync 。让我们知道,如果这有什么差别:

Try this implementation, just use client.SendMailExAsync instead of client.SendMailAsync. Let us know if it makes any difference:

public static class SendMailEx
{
    public static Task SendMailExAsync(
        this System.Net.Mail.SmtpClient @this,
        System.Net.Mail.MailMessage message,
        CancellationToken token = default(CancellationToken))
    {
        // use Task.Run to negate SynchronizationContext
        return Task.Run(() => SendMailExImplAsync(@this, message, token));
    }

    private static async Task SendMailExImplAsync(
        System.Net.Mail.SmtpClient client, 
        System.Net.Mail.MailMessage message, 
        CancellationToken token)
    {
        token.ThrowIfCancellationRequested();

        var tcs = new TaskCompletionSource<bool>();
        System.Net.Mail.SendCompletedEventHandler handler = null;
        Action unsubscribe = () => client.SendCompleted -= handler;

        handler = async (s, e) =>
        {
            unsubscribe();

            // a hack to complete the handler asynchronously
            await Task.Yield(); 

            if (e.UserState != tcs)
                tcs.TrySetException(new InvalidOperationException("Unexpected UserState"));
            else if (e.Cancelled)
                tcs.TrySetCanceled();
            else if (e.Error != null)
                tcs.TrySetException(e.Error);
            else
                tcs.TrySetResult(true);
        };

        client.SendCompleted += handler;
        try
        {
            client.SendAsync(message, tcs);
            using (token.Register(() => client.SendAsyncCancel(), useSynchronizationContext: false))
            {
                await tcs.Task;
            }
        }
        finally
        {
            unsubscribe();
        }
    }
}

这篇关于SmtpClient.SendMailAsync抛出一个特定的异常时,导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆