Cod*_*gry 7 .net c# sqlclient async-await .net-7.0
我有与此类似的代码在多个BackgroundServices (.NET 7)中运行。经过几天的运行时间(显然,循环之间的延迟为几分钟(到几小时))SqlConnection会导致数以万计的悬空句柄出现大量内存泄漏(可能所有曾经使用过的句柄仍然被引用,即使正确地与数据库)。我正在使用.NET 的 SqlClient 5.0.1。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using Microsoft.Data.SqlClient;
namespace Memleak;
static class Program
{
const string connectionString = "Server=lpc:localhost;"
+ "Integrated Security=True;Encrypt=False;"
+ "MultipleActiveResultSets=False;Pooling=False;";
static async Task Main(params string[] args)
{
using CancellationTokenSource cts = new();
// not to forget it running
cts.CancelAfter(TimeSpan.FromMinutes(15));
CancellationToken ct = cts.Token;
using Process process = Process.GetCurrentProcess();
long loop = 1;
while (true)
{
await ConnectionAsync(ct);
// this seems to be the issue (delay duration is irrelevant)
await Task.Delay(TimeSpan.FromMilliseconds(1), ct);
process.Refresh();
long workingSet = process.WorkingSet64;
Console.WriteLine("PID:{0} RUN:{1:N0} RAM:{2:N0}",
process.Id, loop, workingSet);
++loop;
}
}
private static async Task ConnectionAsync(CancellationToken ct = default)
{
using SqlConnection connection = new(connectionString);
await connection.OpenAsync(ct);
using SqlCommand command = connection.CreateCommand();
command.CommandText = "select cast(1 as bit);";
using SqlDataReader reader = await command.ExecuteReaderAsync(ct);
if (await reader.ReadAsync(ct))
{
_ = reader.GetBoolean(0);
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下命令提示符命令显示泄漏:
// dotnet tool install --global dotnet-dump
dotnet-dump collect -p pid
dotnet-dump analyze dump_name.dmp
dumpheap -type Microsoft.Data.SqlClient.SqlConnection -stat
dumpheap -mt mtid
dumpobj objid
gcroot objid
Run Code Online (Sandbox Code Playgroud)
System.Threading.CancellationTokenSource+CallbackNode最后一个命令显示一个对象的巨大列表SqlConnection。
这是一个错误还是按预期工作(如果是,为什么)?除了删除所有async代码并仅使用之外,是否有任何简单的解决方法Threads?我无法使用Timers,因为延迟会根据某些因素而变化(当有工作时,延迟会更短;当没有工作时,延迟会更长)。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using Microsoft.Data.SqlClient;
namespace NotMemleak;
static class Program
{
const string connectionString = "Server=lpc:localhost;" +
"Integrated Security=True;Encrypt=False;" +
"MultipleActiveResultSets=False;Pooling=False;";
static void Main(params string[] args)
{
using CancellationTokenSource cts = new();
// not to forget it running
cts.CancelAfter(TimeSpan.FromMinutes(15));
CancellationToken ct = cts.Token;
using Process process = Process.GetCurrentProcess();
long loop = 1;
while (loop < 1000)
{
Connection();
// this seems to be the issue (delay duration is irrelevant)
ct.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(1));
// Thread.Sleep();
process.Refresh();
long workingSet = process.WorkingSet64;
Console.WriteLine("PID:{0} RUN:{1:N0} RAM:{2:N0}"
, process.Id, loop, workingSet);
++loop;
}
Console.WriteLine();
Console.WriteLine("(press any key to exit)");
Console.ReadKey(true);
}
private static void Connection()
{
using SqlConnection connection = new(connectionString);
connection.Open();
using SqlCommand command = connection.CreateCommand();
command.CommandText = "select cast(1 as bit);";
using SqlDataReader reader = command.ExecuteReader();
if (reader.Read())
{
_ = reader.GetBoolean(0);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我相信这与 GitHub 中的这个问题有关。据我了解,这是 SqlClient 5.0.1 中引入的回归。基本上,在这条线上:
await reader.ReadAsync(ct)
Run Code Online (Sandbox Code Playgroud)
你传递一个token,当这个token被取消时,阅读器会在内部注册一个回调函数。但是,此注册并未在所有代码路径上正确取消注册。这反过来会导致您的SqlConnection实例可以CancellationTokenSource通过该回调注册(引用数据读取器、引用命令、引用连接)来访问。
此问题已在 5.1.0 中修复,该版本已于 2023-01-19 发布稳定版本。
| 归档时间: |
|
| 查看次数: |
570 次 |
| 最近记录: |