问题
I'm developing .net core 2.0 application with gRPC and find out a problem: after delete reference to instance of my gRPC client class, there still channel that use resourses (memory and processor). Example code:
public class MyClient : ClientBase
{
public MyClient(Channel channel) : base(channel)
{
}
}
internal class Program
{
private static void Main(string[] args)
{
var list = new List<MyClient>();
for (var i = 0; i < 10000; i++)
{
Console.WriteLine($"Creating {i} instance");
list.Add(new MyClient(new Channel("127.0.0.1:61783", ChannelCredentials.Insecure)));
}
Console.WriteLine("press enter to list = null");
Console.ReadLine();
list = null;
Console.WriteLine("press enter to GC.Collect();");
Console.ReadLine();
GC.Collect();
Console.WriteLine("press enter to exit");
Console.ReadLine();
}
}
If u run example, u'll see that 10% (on my PC) used by this application. Even after list = null and GC.Collect()
The reason i suppose is ClientBase do not call Channel.ShutdownAsync().
So the question is:
What is the better way to resolve problem?
p.s. actually i use a "generated by the protocol buffer compiler" Client
Client: Grpc.Core.ClientBase<TDto>
and i can't explicitly change finalizer in generated class
回答1:
Possible suggestion would be to make the client implement IDisposable
and in the Dispose
method call the Channel.ShutdownAsync()
.
public class MyClass : Client, IDisposable {
Channel channel;
private bool _isDisposed = false;
private readonly object _lock = new object();
public MyClass(Channel channel)
: base(channel) {
this.channel = channel;
this.channelDisposing += onDisposing;
}
public Channel Channel { get { return channel; } }
private event EventHandler channelDisposing = delegate { };
async void onDisposing(object sender, EventArgs e) {
await channel.ShutdownAsync();
channel = null;
}
public void Dispose() {
if (!_isDisposed) {
Dispose(true);
GC.SuppressFinalize(this);
}
}
void Dispose(bool disposing) {
// No exception should ever be thrown except in critical scenarios.
// Unhandled exceptions during finalization will tear down the process.
if (!_isDisposed) {
try {
if (disposing) {
// Acquire a lock on the object while disposing.
if (channel != null) {
lock (_lock) {
if (channel != null) {
channelDisposing(this, EventArgs.Empty);
}
}
}
}
} finally {
// Ensure that the flag is set
_isDisposed = true;
}
}
}
}
This will allow you to now call Dispose
on the clients and release resources or wrap them in using
so that it will be done for you when they go out of scope.
public class Program {
public static void Main(string[] args) {
var list = new List<MyClient>();
for (var i = 0; i < 10000; i++) {
Console.WriteLine($"Creating {i} instance");
list.Add(new MyClient(new Channel("127.0.0.1:61783", ChannelCredentials.Insecure)));
}
Console.WriteLine("press enter to dispose clients");
Console.ReadLine();
list.ForEach(c => c.Dispose());
Console.WriteLine("press enter to list = null");
Console.ReadLine();
list = null;
Console.WriteLine("press enter to GC.Collect();");
Console.ReadLine();
GC.Collect();
Console.WriteLine("press enter to exit");
Console.ReadLine();
}
}
来源:https://stackoverflow.com/questions/47594441/grpc-client-do-not-dispose-channel