gRPC客户端不处理Channel

时间:2017-12-01 13:25:24

标签: c# memory-leaks asp.net-core grpc

我正在使用gRPC开发.net core 2.0应用程序并找出问题:在删除对gRPC客户端类实例的引用之后,仍然存在使用资源(内存和处理器)的通道。 示例代码:

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();
        }
    }

如果您运行示例,您将看到此应用程序使用的10%(在我的PC上)。即使在 list = null GC.Collect()

之后

我认为 ClientBase 的原因是不要调用 Channel.ShutdownAsync()

所以问题是:

解决问题的更好方法是什么?

P.S。实际上我使用“由协议缓冲编译器生成”客户端

Client: Grpc.Core.ClientBase<TDto>

我无法在生成的类中明确更改终结器

1 个答案:

答案 0 :(得分:0)

可能的建议是让客户端实现IDisposable并在Dispose方法中调用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;
            }
        }
    }
}

这将允许您现在在客户端上调用Dispose并释放资源或将其包装在using中,以便在它们超出范围时为您完成。

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();
    }
}