博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
尝试解决在构造函数中同步调用Dns.GetHostAddressesAsync()引起的线程死锁
阅读量:5940 次
发布时间:2019-06-19

本文共 5208 字,大约阅读时间需要 17 分钟。

(最终采用的是)

问题详情见:

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

Linux:

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

Windows(1.3万个线程):

引发问题的代码:

Task
task = System.Net.Dns.GetHostAddressesAsync(host);task.Wait(5000);var addresses = task.Result;

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

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

接下来尝试解决方法。

1)参考  ,将上面的代码改为:

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

死锁问题依旧。

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

private static async Task
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
GetHostAddressesAsyncProxy(string host){ return await System.Net.Dns.GetHostAddressesAsync(host);}

死锁依旧。 

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

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

改为同步方法

public static Task
GetHostAddressesAsync(string hostNameOrAddress){ NameResolutionPal.EnsureSocketsAreInitialized(); return Task.FromResult
(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
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); } }}

相关链接:

转载地址:http://asmtx.baihongyu.com/

你可能感兴趣的文章
python-while循环
查看>>
手机端上传图片及java后台接收和ajaxForm提交
查看>>
【MSDN 目录】C#编程指南、C#教程、ASP.NET参考、ASP.NET 4、.NET Framework类库
查看>>
jquery 怎么触发select的change事件
查看>>
angularjs指令(二)
查看>>
(原創) 如何建立一个thread? (OS) (Linux) (C/C++) (C)
查看>>
<气场>读书笔记
查看>>
领域驱动设计,构建简单的新闻系统,20分钟够吗?
查看>>
web安全问题分析与防御总结
查看>>
React 组件通信之 React context
查看>>
ZooKeeper 可视化监控 zkui
查看>>
Linux下通过配置Crontab实现进程守护
查看>>
ios 打包上传Appstore 时报的错误 90101 90149
查看>>
Oracle推出轻量级Java微服务框架Helidon
查看>>
密码概述
查看>>
autoconf,automake,libtool
查看>>
jQuery的技巧01
查看>>
基于泛型实现的ibatis通用分页查询
查看>>
gopacket 使用
查看>>
AlertDialog对话框
查看>>