(最终采用的是方法4

问题详情见:.NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长

看看在 Linux 与 Windows 上发生线程死锁的后果。

Linux:

Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException: Error -24 EMFILE too many open files

Windows(1.3万个线程):

引发问题的代码:

Task<IPAddress[]> task = System.Net.Dns.GetHostAddressesAsync(host);
task.Wait();
var addresses = task.Result;

上面的代码是在构造函数中调用的,只能同步调用,无法异步调用。

踩坑的条件:在一定数量的请求并发时才出现,如果只有很少的请求不会出现。所以,当我们发布时,将服务器从负载均衡上摘下来,结束进程,更新程序,在本机访问后(host解析已完成)挂上负载均衡,问题不会出现。如果不从负载均衡上摘下来,直接结束 asp.net core 程序的进程,新启动的进程就会出现这个问题。

接下来尝试解决方法。

1)参考 Synchronously waiting for an async operation, and why does Wait() freeze the program here ,将上面的代码改为:

var task = Task.Run(async () => { return await System.Net.Dns.GetHostAddressesAsync(host); });
task.Wait();
var addresses = task.Result;

死锁问题依旧。

2)参考 System.Data.SqlClient 中的实现:

private static async Task<Socket> ConnectAsync(string serverName, int port)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(serverName, port).ConfigureAwait(false);
return socket;
} // On unix we can't use the instance Socket methods that take multiple endpoints IPAddress[] addresses = await Dns.GetHostAddressesAsync(serverName).ConfigureAwait(false);
return await ConnectAsync(addresses, port).ConfigureAwait(false);
}

(注:SqlClient中在Windows上没有调用Dns.GetHostAddressesAsync)

将 Dns.GetHostAddressesAsync 放在一个 async/await 代理方法中:

private static async Task<IPAddress[]> GetHostAddressesAsyncProxy(string host)
{
return await System.Net.Dns.GetHostAddressesAsync(host);
}

死锁依旧。

3)修改 System.Net.Dns 的源代码,将异步方法

public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress)
{
NameResolutionPal.EnsureSocketsAreInitialized();
return Task<IPAddress[]>.Factory.FromAsync(
(arg, requestCallback, stateObject) => BeginGetHostAddresses(arg, requestCallback, stateObject),
asyncResult => EndGetHostAddresses(asyncResult),
hostNameOrAddress,
null);
}

改为同步方法

public static Task<IPAddress[]> GetHostAddressesAsync(string hostNameOrAddress)
{
NameResolutionPal.EnsureSocketsAreInitialized();
return Task.FromResult<IPAddress[]>(GetHostEntry(hostNameOrAddress).AddressList);
}

问题解决!

说明死锁问题的确是由于在构造函数中同步调用异步方法引起的。目前 System.Net.NameResolution 只提供了异步的 API 进行主机名的解析,上面的 GetHostEntry() 是同步方法,但只支持 netstandard2.0 ,目前 nuget.org 上的 System.Net.NameResolution 只支持到 netstandard 1.3 。

[备注]

---------------

修改 System.Net.Dns 的源代码,生成程序集(System.Net.NameResolution)并更新至 asp.net core 程序中的方法:

1)在github上签出corefx的源代码

2)修改 System.Net.Dns 的源代码

3)运行corefx文件夹中的init-tools.cmd命令

4)运行 MSBuild Command Prompt for VS2015 命令行,进入 corefx\src\System.Net.NameResolution\src 目录,运行 msbuild System.Net.NameResolution.builds 命令,会在 corefx\bin\Windows_NT.AnyCPU.Debug\System.Net.NameResolution\netcore50 文件夹中生成对应的程序集 System.Net.NameResolution.dll 。

5)将上一步生成的 System.Net.NameResolution.dll 复制到 asp.net core 站点的文件夹替换已有的同名文件即可。

---------------

4)尝试不修改 System.Net.Dns 的源代码进行解决

同步的  System.Net.Dns.GetHostEntry(string hostNameOrAddress)  方法可以解决问题,但它是为 netstandard2.0 api 实现的,在基于 netstandard1.6 的程序中无法直接调用,编译不通过。实际的 System.Net.NameResolution.dll 程序集中已经包含了 GetHostEntry() 实现,虽然编译时不让调用,但我们可以在运行时调用,那运行时如何调用呢?“反射”闪亮登场,用反射改为下面的代码:

var method = typeof(System.Net.Dns).GetMethod("GetHostEntry", BindingFlags.Public | BindingFlags.Static);
var addresses = ((IPHostEntry)method.Invoke(null, new object[] { host })).AddressList;

但发现 NuGet 服务器上发布的 System.Net.NameResolution 4.3.0 中并不包含 GetHostEntry() 这个方法。后来找到了另外一个私有静态方法 —— InternalGetHostByName() 。再后来发现 System.Net.DnsEndPoint ,使用它就不需要自己进行主机名的解析,但目前只支持 Windows 。

于是最终采取的方法是:Windows 平台用 DnsEndPoint ,非 Windows 平台用反射调用 System.Net.Dns.InternalGetHostByName() 方法。示例代码如下:

private void ConnectWithTimeout(Socket socket, EndPoint endpoint, int timeout)
{
if (endpoint is DnsEndPoint && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
IPAddress[] addresses;
var dnsEndPoint = ((DnsEndPoint)endpoint);
var host = dnsEndPoint.Host;
var method = typeof(System.Net.Dns).GetTypeInfo()
.GetMethod("InternalGetHostByName", BindingFlags.NonPublic | BindingFlags.Static);
if (method != null)
{
addresses = ((IPHostEntry)method.Invoke(null, new object[] { host, false })).AddressList;
}
else
{
Task<IPAddress[]> task = Dns.GetHostAddressesAsync(host);
task.Wait(timeout);
addresses = task.Result;
} var address = addresses.FirstOrDefault(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
if (address == null)
{
throw new ArgumentException(String.Format("Could not resolve host '{0}'.", host));
}
endpoint = new IPEndPoint(address, dnsEndPoint.Port);
} var completed = new AutoResetEvent(false);
var args = new SocketAsyncEventArgs();
args.RemoteEndPoint = endpoint;
args.Completed += OnConnectCompleted;
args.UserToken = completed;
socket.ConnectAsync(args);
if (!completed.WaitOne(timeout) || !socket.Connected)
{
using (socket)
{
throw new TimeoutException("Could not connect to " + endpoint);
}
}
}

相关链接:

在同步方法中调用异步方法时如何避免死锁问题

尝试解决在构造函数中同步调用Dns.GetHostAddressesAsync()引起的线程死锁的更多相关文章

  1. C++构造函数中不能调用虚函数

    在构造函数中调用虚函数,并不会产生多态的效果,就跟普通函数一样. c++ primer 第四版中497页15.4.5构造函数和析构中的虚函数讲到,如果在构造函数或析构函数中调用虚函数,则运行的是为构造 ...

  2. 解决在构造函数中使用Session,Session为null的问题

    问题描述: public abstract class PageBase : System.Web.UI.Page 在PageBase中如何使用Session??? 我直接用 Session[&quo ...

  3. 关于父类私有属性在子类构造函数中super调用的解释

    package test; public class Car { private int carMoney; //汽车租金 private String carName; //汽车名字 private ...

  4. 你的眼睛背叛你的心:解决 .NET Core 中 GetHostAddressesAsync 引起的 EnyimMemcached 死锁问题

    在我们将站点从 ASP.NET + Windows 迁移至 ASP.NET Core + Linux 的过程中,目前遇到的最大障碍就是 —— 没有可用的支持 .NET Core 的 memcached ...

  5. .NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长

    一个 asp.net core 站点,之前运行在Linux 服务器上,运行一段时间后有时站点会挂掉,在日志中记录很多“EMFILE too many open files”的错误: Microsoft ...

  6. win2008加入域控之尝试解析加入域中域控制器的dns名称失败解决办法

    记录下今天遇到以前没遇到的问题 加入域的时候提示“尝试解析加入域中控制器的DNS”名称失败 可能的原因: 如果确认dns没问题 dc正常访问,那可能就是因为域控制器无法向dns注册srv记录. SRV ...

  7. C#中的BackgroundWorker控件+Delegate.Invoke (委托同步调用)

    C#中的BackgroundWorker控件+Delegate.Invoke (委托同步调用) 简单代码,记录一下.一个BackgroundWorker控件  backgroundWorkerRefr ...

  8. java中父类与子类, 不同的两个类中的因为构造函数由于递归调用导致栈溢出问题

    /* 对于类中对成员变量的初始化和代码块中的代码全部都挪到了构造函数中, 并且是按照java源文件的初始化顺序依次对成员变量进行初始化的,而原构造函数中的代码则移到了构造函数的最后执行 */ impo ...

  9. 关于在C#中构造函数中调用虚函数的问题

    在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...

随机推荐

  1. oracle去除重复字段

    ) 代码摘自百度.

  2. python的Template

    Template模块,可以用来制作web页面的模板,非常的方便. Template属于string中的一个类,所以要使用的话要在头部引入: from string import Template 模板 ...

  3. python 学习第二天

    由于换了博客,第一篇没有在博客园写,写在了开源中国上,链接地址为http://my.oschina.net/u/254063/blog/719289,大家有兴趣可以看看 一, python 数据类型 ...

  4. DOM事件

    在慕课网上学习了DOM事件探秘课程,特此整理了一下笔记. 慕课网DOM事件探秘课程地址:http://www.imooc.com/learn/138 事件 是文档或浏览器窗口中发生的特定的交互瞬间.[ ...

  5. HTML5的 input:file上传类型控制

    一.input:file属性 属性值有以下几个比较常用: accept:表示可以选择的文件MIME类型,多个MIME类型用英文逗号分开,常用的MIME类型见下表. multiple:是否可以选择多个文 ...

  6. Swift学习之熟悉控件

    最近是比较清闲一些的,对于一个开发者来说,这也是一个很好的充电机会.以前做项目都是使用Objective-C去开发,但我们都知道,Swift语言从2014年的出现到现在,一步一步变的完善,渐渐变的受欢 ...

  7. [资料分享]nutch视频教程

  8. 用PS如何把图片调出时尚杂志色

    摘自:http://www.3lian.com/edu/2013/07-22/83061.html 01:打开图片,执行调整图层-色彩平衡;调整图层的标记-红色方框内图标. 02:色彩平衡-设置-点选 ...

  9. kindeditor-4.1.3工具使用技巧:如何在编辑区上传图片并保存绝对路径

    MVC项目开中用到了KindEditor图片上传工具,需要在编辑区内上传图片,并将图片的URL保存为符合如下格式地址,如http://192.168.0.111/uploadImg/imgName.j ...

  10. 【BZOJ】4002: [JLOI2015]有意义的字符串

    题意 求$\left \lfloor \left( \frac{b+\sqrt{d}}{2} \right)^n \right \rfloor \pmod {7528443412579576937} ...