Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-mvc/15.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# SmtpClient.SendMailAsync在引发特定异常时会导致死锁_C#_Asp.net Mvc_Async Await_Asp.net Identity_Smtpclient - Fatal编程技术网

C# SmtpClient.SendMailAsync在引发特定异常时会导致死锁

C# SmtpClient.SendMailAsync在引发特定异常时会导致死锁,c#,asp.net-mvc,async-await,asp.net-identity,smtpclient,C#,Asp.net Mvc,Async Await,Asp.net Identity,Smtpclient,我正在尝试根据VS2013项目模板中的示例AccountController为ASP.NET MVC5网站设置电子邮件确认。我已经使用SmtpClient实现了IIdentityMessageService,试图让它尽可能简单: public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { using(var

我正在尝试根据VS2013项目模板中的示例AccountController为ASP.NET MVC5网站设置电子邮件确认。我已经使用
SmtpClient
实现了
IIdentityMessageService
,试图让它尽可能简单:

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);
        }
    }
}
调用它的控制器代码直接来自模板(提取到单独的操作中,因为我想排除其他可能的原因):

公共异步任务); 返回视图(); } 然而,当邮件无法发送时,我会有奇怪的行为,但只有在一个特定的实例中,当主机无法访问时。示例配置:

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

在这种情况下,请求似乎处于死锁状态,从不向客户端返回任何内容。如果邮件由于任何其他原因(例如主机主动拒绝连接)而无法发送,则异常会正常处理,我会收到YSOD

查看Windows事件日志,似乎在同一时间段内抛出了一个
invalidoOperationException
,消息为“异步操作仍挂起时异步模块或处理程序已完成”。"; 如果我试图捕获控制器中的
SmtpException
,并在catch块中返回
ViewResult
,则在YSOD中会收到相同的消息。因此,我认为无论哪种情况,
wait
-ed操作都无法完成

据我所知,我正在遵循其他关于SO的帖子中概述的所有async/await最佳实践(例如),主要是“一直使用async/await”。我还尝试过使用
ConfigureAwait(false)
,没有任何更改。因为只有在抛出特定异常时代码才会死锁,所以我认为一般模式在大多数情况下都是正确的,但是在这种情况下,内部发生了一些事情,使得它不正确;但由于我对并发编程还很陌生,我觉得我可能错了


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

尝试此实现,只需使用
client.SendMailExAsync
而不是
client.SendMailAsync
。如果有什么不同,请告诉我们:

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();
        }
    }
}
公共静态类SendMailEx
{
公共静态任务SendMailExAsync(
此System.Net.Mail.SmtpClient@this,
System.Net.Mail.MailMessage消息,
CancellationToken=默认值(CancellationToken))
{
//使用Task.Run否定SynchronizationContext
返回Task.Run(()=>SendMailExImplAsync(@this,message,token));
}
专用静态异步任务SendMailExImplAsync(
System.Net.Mail.SmtpClient客户端,
System.Net.Mail.MailMessage消息,
取消令牌(令牌)
{
token.ThrowIfCancellationRequested();
var tcs=new TaskCompletionSource();
System.Net.Mail.SendCompletedEventHandler=null;
操作取消订阅=()=>client.sendpleted-=处理程序;
handler=async(s,e)=>
{
取消订阅();
//异步完成处理程序的一种方法
等待任务;
if(例如UserState!=tcs)
tcs.TrySetException(新的InvalidOperationException(“意外用户状态”);
否则(如已取消)
tcs.trysetconceled();
否则如果(例如错误!=null)
tcs.TrySetException(即错误);
其他的
tcs.TrySetResult(真);
};
client.sendpleted+=处理程序;
尝试
{
sendaync(消息,tcs);
使用(token.Register(()=>client.SendAsyncCancel(),useSynchronizationContext:false))
{
等待tcs任务;
}
}
最后
{
取消订阅();
}
}
}

看一看。异步Void方法有时会出现问题。@ErikPhilips-我在示例中没有看到任何
Async Void
方法(无论是实现的还是调用的)-您是指某一行吗?作为解决方法,您可以尝试手动解决主机问题,并在较早时失败。。。另外,请查看以获取见解-希望这会有所帮助…我记得有一个相关的问题,其中有一个解决方法。。。这就是:@regexen,试试我的
,不带任何上下文,看看有没有什么不同。那一个有效;异常被捕获,就像人们通常期望的那样,在调用堆栈中冒泡,我得到一个YSOD。似乎有很多代码可以完成一些看起来很简单的事情(!),但我可以看到它是如何快速变得复杂的。无论如何,标记为已接受,因为它确实解决了问题。谢谢你的帮助!很高兴它能起作用;这是一个典型的基于
任务的包装器。我想知道如果没有
wait Task.Yield()
,它是否仍然可以工作,也许您可以尝试一下。根据快速测试,似乎没有
Task.Yield
,它仍然可以工作。根据您当前的实现,您在执行IO操作时仍然在“浪费”线程@我不是
Task.Run(()=>FuncAsync())
Task.Run(async()=>wait FuncAsync())
基本相同,但没有增加异步状态机的微开销。在这两种情况下都使用相同的方法。@BornToCode,no
Task.Run
不会对返回
Task
的函数(如
sendmaileximplsync
)进行任何阻止(除非该函数内部有阻止等待,而我的函数没有)。如果您想了解更多,请查看。
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();
        }
    }
}