2017-12-01 20 views
2

gRPC를 사용하여 .net core 2.0 응용 프로그램을 개발 중이며 문제를 발견했습니다. gRPC 클라이언트 클래스의 인스턴스에 대한 참조를 삭제 한 후에 resourses (메모리 및 프로세서)를 사용하는 채널이 여전히 존재합니다. 예제 코드 : 유 예제를 실행하면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(); 
     } 
    } 

, u'll (내 PC에) 10 %가이 응용 프로그램에서 사용하는 것을 알 수있다. 심지어 목록 = null이하는 GC.Collect() 후

내가 생각하는 이유는 Channel.ShutdownAsync()를 호출하지 않는 ClientBase입니다.

그래서 질문은 :

문제를 해결하기 위해 더 나은 방법은 무엇입니까?

p.s. 실제로 나는 클라이언트

Client: Grpc.Core.ClientBase<TDto> 

"프로토콜 버퍼 컴파일러에 의해 생성 된"사용하고 난 명시 적으로 생성 된 클래스에서 종료자를 변경할 수 없습니다

+0

채널을 직접 종료하지 않는 이유는 무엇입니까? – Evk

+0

최상의 솔루션이라고 확신하지 않습니다. {} 또는 finilize() 메소드를 사용하여 리소스를 폐기하는 것이 가장 좋습니다. –

+4

타사 라이브러리를 사용하고 있는데이 라이브러리는 이와 같이 사용하지 않는 것이 좋습니다 (거기에'IDisposable', 즉 짝수 채널을 구현하는 것도 없으므로)이 경우 가장 좋은 방법은 채널을 완료했을 때 채널을 명시 적으로 종료하고 가비지 수집기와 같은 다른 방법을 사용하지 않을 때입니다. 음, 어쨌든 자신을 풀어 놓을 수있는 자원을 공개하기 위해 GC에 의존하지 않는 것이 가장 좋습니다. – Evk

답변

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

Grpc에 의해 자동 생성 된 클래스 MyClient를 변경할 수 없습니다 (내 ps 참조). Grpc 생성기를 사용하여 변경하는 방법을 알고있는 경우에만 .... –

+0

@TimurLemeshko 그런 다음 생성 된 클래스가 봉인되지 않았다면 MyClass에서 상속받은 다른 클래스를 만들고 IDisposable을 구현합니다. 내 가정은 당신이'MyClass' 코드를 통제하고 있었다는 것입니다. – Nkosi

+3

나는이 일을 정말로 좋아하지 않는다. dispose ('GetAwaiter(). GetResult()'와 함께)에서'Task'를 반환하는 호출 메소드가 올바르게 보이지 않습니다.호출자가'ShutdownAsync'에 직접 접근 할 수있게 해주면,'Task.WhenAll()'과 같은 일을 할 수 있고, 일반적으로 이미 비동기 코드베이스에 통합 할 수 있습니다. – Evk