这是在将 memcached 客户端 EnyimMemcached 迁移至 .Net Core 遇到的问题。
在 MemcachedClient 的构造函数中创建 socket 连接池时,有一个将主机名解析为 IP 地址的操作,之前调用的是同步的 System.Net.Dns.GetHostEntry() 方法,但在 .NET Core 中只有异步的 System.Net.Dns.GetHostAddressesAsync() 方法,所以被迫改为这样调用:
var addresses = System.Net.Dns.GetHostAddressesAsync(host).Result;
结果只要使用 EnyimMemcached 的 ASP.NET Core 站点在 Linux 上一运行,多个请求进来,就发生死锁(deadlock)。从浏览器端看,请求发出后,就一直处于等待状态。
不管改为:
var addresses = GetHostAddressesAsync(host).ConfigureAwait(false).GetAwaiter().GetResult();
还是改为:
var addresses = await Task.Run(async () =>
{
return await System.Net.Dns.GetHostAddressesAsync(host);
});
都会发生死锁。
你也许会说,干脆将上层调用方法都改成异步的,但是最上层是 MemcachedClient 的构造函数,没法改为异步的。如果真要将创建 socket 连接池的操作改为异步的,需要对 EnyimMemcached 的代码进行大动作。
目前还是打算直接在同步方法中调用异步方法,所以请教一下大家看有没有什么法子避开死锁?
2016年12月11日更新:
更新2:详见 你的眼睛背叛你的心:解决 .NET Core 中 GetHostAddressesAsync 引起的 EnyimMemcached 死锁问题
更新1:在添加补充问题后突然发现Dns.GetHostAddressesAsync()
没有使用async关键字。
最终发现这个问题竟然是因为在MVC视图中调用了异步方法(弄错了,不是这个原因):
@inject CNBlogs.AboutUs.Application.ITabNavService tabNavService
<div id="side_left">
<ul id="nav_block">
@foreach (var nav in (await tabNavService.GetAll()))
{
<p>@Html.TabLink(nav.Title, nav.Url, nav.ActionName)</p>
}
</ul>
</div>
改为在控制器Action中调用就没这个问题了。
在视图中调用异步方法是ASP.NET Core MVC的一个新特性,没想到有这个坑。
汗,搞错!不是这个原因,请求量上去后,死锁问题又出现了。早上只是请求量少,发生死锁的概率降低了。
同步方法中调用异步方法的参考资料:How would I run an async Task
如果在异步completed事件中实例化MemcachedClient类可行吗
MemcachedClient的实例创建是通过依赖注入
private async void Init()
{ var addresses = await Task.Run(async () => { return await System.Net.Dns.GetHostAddressesAsync(host); }); }
构造函数李调用这个Init();可以试试,
这样调用,如果Init()方法执行中出错,调用方就不知道
@dudu: 出错的话可以抛事件,
var task=System.Net.Dns.GetHostAddressesAsync(host)
;
task.ContinueWith(_=>
{
var iplists=task.Result;
....
});
使用ContinueWith()要使用Wait(),一用Wait()也是同样的问题
var continueTask = task.ContinueWith(_ =>
{
var addresses = task.Result;
});
continueTask.Wait();
还是http async content的问题。task指定一个新的async content。不使用http上下文的asynccontent。
Hi, dudu
关于 Dns.GetHostAddressesAsync()
的问题,其实是这样的:
你说的慢的问题,并不是死锁,也不是因为异步的问题,而且线程池的线程耗尽了,在被阻塞等待处理(理解为死锁了也没有太大关系)。
Dns.GetHostAddressesAsync
的内部实现 它其实是一个包装器,通过阻塞的方式包装了一个线程池队列,然后当有大量请求的时候,线程池的线程已经不够用了,也就是没了空闲的线程去处理新请求了,自然就会别阻塞等待新线程释放。所以会变得很慢,甚至卡死。。
见代码 :
http://referencesource.microsoft.com/#System/net/System/Net/DNS.cs,744
解决方法就是:
Windows 操作系统和 Linux 操作系统都提供了 真正的 异步的 解析 DNS 的实现,可以使用操作系统的。
windows dns api: https://msdn.microsoft.com/en-us/library/ms738524(VS.85).aspx
linux dns api: http://www.binarytides.com/dns-query-code-in-c-with-linux-sockets/
关于同步异步的IO问题,可以参见这个讨论:
https://github.com/dotnet/corefx/issues/5044
给分吧,哈哈,不谢。
我试试调用Linux的getent命令进行解析
调用getent命令解析成功
var psi = new System.Diagnostics.ProcessStartInfo("getent", " hosts " + host);
psi.RedirectStandardOutput = true;
using (var process = System.Diagnostics.Process.Start(psi))
{
var output = process.StandardOutput.ReadToEnd();
if(!string.IsNullOrWhiteSpace(output))
{
var ipString = Regex.Match(output, @"^\d+\.\d+\.\d+\.\d+").Value;
if(!string.IsNullOrEmpty(ipString))
{
address = IPAddress.Parse(ipString);
}
}
}
不是“线程池的线程耗尽了”的原因,在异步方法调用Dns.GetHostAddressesAsync()
没这个问题。
@dudu: dudu,我刚才测试了一下, 在10个以下连结的时候,响应时间都在5ms以内,随着连接数的增多,响应时间逐渐变长,当到60个线程的时候,响应时间35-45毫秒了。 我测试用的Web程序的Action是异步方法,负载工具用的Load Runner。
@Savorboard: 即使有这个问题,也影响不大,只是在初始化socket池或者创建新的socket连接时会调用SocketDns.GetHostAddressesAsync()
,而且dns解析本身就有缓存。
学习了!
目前我见到的最高的豆~哈哈~我是冲着豆进来看的
不知所云。
Task.Factory.StartNew(() => DoAsync()).Unwrap().GetAwaiter().GetResult()