内容简介:最近遇到一个非常诡异的问题,在一个 ASP.NET Core 2.2 项目中,从 SQL Server 数据库查询 100 条数据记录,会出现 16-22s 左右的延迟。延迟出现在执行 SqlDataReader 的 ReadAsync 方法时,在一行一行读取数据时,读取某些行时会出现 2-3s 的延迟。前两天通过在 corefx 中 System.Data.SqlClient 源码中打点发生延迟时 ContinueWith 中的任务会延迟执行,延迟发生在 completionSource.Task 中,c
最近遇到一个非常诡异的问题,在一个 ASP.NET Core 2.2 项目中,从 SQL Server 数据库查询 100 条数据记录,会出现 16-22s 左右的延迟。延迟出现在执行 SqlDataReader 的 ReadAsync 方法时,在一行一行读取数据时,读取某些行时会出现 2-3s 的延迟。
前两天通过在 corefx 中 System.Data.SqlClient 源码中打点 Console.WriteLine($ " {DateTime.Now} " ) (打点方法见之前的博文),定位到了延迟出现在 SqlDataReader.cs 的 ContinueRetryable 方法下面的代码处。
return completionSource.Task.ContinueWith((retryTask) => { //... }
发生延迟时 ContinueWith 中的任务会延迟执行,延迟发生在 completionSource.Task 中,completionSource 的类型是 TaskCompletionSource<object> ,值来自 _stateObj._networkPacketTaskSource ,_stateObj 的类型是 TdsParserStateObject 。
接下来进入 TdsParserStateObject.cs 进行追踪,追踪什么地方进行对 TaskCompletionSource 进行了 SetResult 或者 TrySetResult 操作,ContinueWith 就是在 SetResult/TrySetResult 之后执行的。
通过打点发现是在 ReadAsyncCallback<T>(IntPtr key, T packet, UInt32 error) 方法中进行了 TrySetResult 操作:
if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) { if (error == 0) { if (_executionContext != null) { ExecutionContext.Run(_executionContext, (state) => source.TrySetResult(null), null); } else { source.TrySetResult(null); } } //... }
同时打点时间戳信息显示 ReadAsyncCallback 方法中没有发生延迟,说明延迟发生在调用 ReadAsyncCallback 方法之前。
经过无数次的打点,终于发现延迟发生在 SNIPacket.cs 的 ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback) 方法中的那行 stream.ReadAsync 代码:
stream.ReadAsync(_data, 0, _capacity, CancellationToken.None).ContinueWith(t => { //... }
沿着 ReadFromStreamAsync 继续追踪,弄明白 stream.ReadAsync 延迟为什么会造成 source.TrySetResult 延迟?
stream.ReadAsync 的延迟造成了紧随其后的 ContinueWith 延迟执行,ContinueWith 中执行了下面的 callback
callback(this, error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS);
callback 的值是通过 ReadFromStreamAsync 的方法参数传递进来的,传参操作发生在 SNITcpHandle.cs 中的 ReceiveAsync(ref SNIPacket packet) 方法中
public override uint ReceiveAsync(ref SNIPacket packet) { packet = new SNIPacket(_bufferSize); try { packet.ReadFromStreamAsync(_stream, _receiveCallback); return TdsEnums.SNI_SUCCESS_IO_PENDING; } //... }
继续追踪
SNITcpHandle.ReceiveAsync() -> SNIProxy.ReadAsync() -> TdsParserStateObjectManaged.ReadAsync() -> TdsParserStateObject.ReadSni()
来到了 TdsParserStateObject.cs 的 ReadSni() 方法
handle = SessionHandle; if (handle != null) { IncrementPendingCallbacks(); readPacket = ReadAsync(out error, ref handle); if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) { DecrementPendingCallbacks(false); // Failure - we won't receive callback! } }
handle 的值是来自 SessionHandle ,继续追,来到了 TdsParserStateObjectManaged.cs 的 CreatePhysicalSNIHandle 方法
internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity) { _sessionHandle = SNIProxy.Singleton.CreateConnectionHandle(this, serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity); if (_sessionHandle == null) { _parser.ProcessSNIError(this); } else if (async) { // Create call backs and allocate to the session handle SNIAsyncCallback ReceiveAsyncCallbackDispatcher = new SNIAsyncCallback(ReadAsyncCallback); SNIAsyncCallback SendAsyncCallbackDispatcher = new SNIAsyncCallback(WriteAsyncCallback); _sessionHandle.SetAsyncCallbacks(ReceiveAsyncCallbackDispatcher, SendAsyncCallbackDispatcher); } }
通过上面的 ReadAsyncCallback 追踪到了前面提到的进行 TrySetResult 操作的 ReadAsyncCallback 方法。
internal void ReadAsyncCallback(SNIPacket packet, UInt32 error) => ReadAsyncCallback(IntPtr.Zero, packet, error);
通过源码追踪也确认了延迟的确是来自 SNIPacket.ReadFromStreamAsync 中的 stream.ReadAsync 操作,而 stream 的值是一个 NetworkStream 的实例(详见 SNITcpHandle 的构造函数)。
为什么读取 NetworkStream 的过程中发生了延迟?这是接下来需要进一步排查的问题。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 每日一博 | 源码阅读之解读 MyBatis 延迟加载实现原理
- RabbitMQ延迟消息的延迟极限是多少?
- RabbitMQ延迟消息的延迟极限是多少?
- 延迟静态绑定——static
- RabbitMQ实现延迟队列
- mybatis 延迟加载
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
第一行代码:Android(第2版)
郭霖 / 人民邮电出版社 / 2016-12-1 / CNY 79.00
本书被广大Android 开发者誉为“Android 学习第一书”。全书系统全面、循序渐进地介绍了Android软件开发的必备知识、经验和技巧。 第2版基于Android 7.0 对第1 版进行了全面更新,将所有知识点都在最新的Android 系统上进行重新适配,使用 全新的Android Studio 开发工具代替之前的Eclipse,并添加了对Material Design、运行时权限、......一起来看看 《第一行代码:Android(第2版)》 这本书的介绍吧!
URL 编码/解码
URL 编码/解码
RGB HSV 转换
RGB HSV 互转工具