2014-12-12 21 views
5

Tôi đã đấu tranh với WCF Proxies. Cách chính xác để vứt bỏ một WCF Proxy là gì? Câu trả lời không phải là tầm thường.Cách chính xác để vứt bỏ một Proxy WCF là gì?

System.ServiceModel.ClientBase vi phạm của Microsoft riêng Vứt bỏ-pattern

không thực hiện IDisposable nên người ta phải thừa nhận rằng nó nên được xử lý hoặc sử dụng trong một -block using. Đây là những thực hành tốt nhất cho bất cứ thứ gì dùng một lần. Tuy nhiên, việc triển khai là rõ ràng, do đó, một người phải bỏ rõ ràng các trường hợp ClientBase đến IDisposable, hãy giải quyết vấn đề.

Các nguồn lớn nhất của sự nhầm lẫn, tuy nhiên, đó là kêu gọi Dispose() trên ClientBase trường hợp mà faulted, thậm chí kênh mà faulted vì họ không bao giờ mở ra ở nơi đầu tiên, sẽ dẫn đến một ngoại lệ được ném. Điều này, chắc chắn, có nghĩa là ngoại lệ có ý nghĩa giải thích lỗi ngay lập tức bị mất khi ngăn xếp thư giãn, phạm vi using kết thúc và Dispose() ném một ngoại lệ vô nghĩa nói rằng bạn không thể vứt bỏ một kênh bị lỗi.

Hành vi trên là anathema đối với mẫu vứt bỏ mẫu nêu rõ rằng đối tượng phải chịu được nhiều cuộc gọi rõ ràng đến Dispose(). (Xem http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx, "... cho phép các phương pháp Dispose(bool) được gọi là nhiều hơn một lần. Phương pháp này có thể chọn để làm gì sau khi cuộc gọi đầu tiên.")

Với sự ra đời của đảo ngược-of-kiểm soát , việc triển khai kém này trở thành một vấn đề thực sự. I.O.C. container (cụ thể là Ninject) phát hiện giao diện IDisposable và gọi Dispose() rõ ràng trên các phiên bản được kích hoạt ở cuối phạm vi tiêm.

Giải pháp: Proxy ClientBase và Intercept gọi Dispose()

Giải pháp của tôi là để proxy ClientBase bởi subclassing System.Runtime.Remoting.Proxies.RealProxy và chiếm quyền điều khiển hoặc cuộc gọi chặn để Dispose(). thay thế đầu tiên của tôi cho Dispose() đi một cái gì đó như thế này:

if (_client.State == CommunicationState.Faulted) _client.Abort(); 
else ((IDisposable)_client).Dispose(); 

(Lưu ý rằng _client là một tài liệu tham khảo đánh máy với mục tiêu của proxy.)

Vấn đề với NetTcpBinding

Tôi nghĩ rằng điều này đã đóng đinh nó, ban đầu, nhưng sau đó tôi phát hiện ra một vấn đề trong sản xuất: trong một số trường hợp khó tạo ra, tôi thấy rằng các kênh sử dụng NetTcpBinding không đóng đúng cách trong trường hợp không có lỗi, mặc dù Dispose đã được gọi trên _client.

Tôi đã có ứng dụng ASP.NET MVC bằng cách triển khai proxy để kết nối với Dịch vụ WCF bằng cách sử dụng NetTcpBinding trên mạng cục bộ, được lưu trữ trong Dịch vụ Windows NT trên một cụm dịch vụ chỉ với một nút. Khi tôi tải thử nghiệm ứng dụng MVC, một số điểm cuối trên Dịch vụ WCF (đang sử dụng chia sẻ cổng) sẽ ngừng đáp ứng sau một thời gian.

Tôi đã cố gắng để tái tạo điều này: các thành phần tương tự chạy trên mạng LAN giữa hai máy của nhà phát triển đã hoạt động hoàn hảo; một ứng dụng giao diện điều khiển đóng búa các điểm cuối WCF thực (chạy trên cụm dịch vụ dàn dựng) với nhiều quy trình và nhiều luồng trong mỗi tác vụ; cấu hình ứng dụng MVC trên máy chủ dàn dựng để kết nối với các điểm cuối trên máy của nhà phát triển làm việc dưới tải; chạy ứng dụng MVC trên máy của nhà phát triển và kết nối với các điểm cuối WCF dàn dựng đã làm việc. Kịch bản cuối cùng chỉ làm việc dưới IIS Express, tuy nhiên, và đây là một bước đột phá. Các thiết bị đầu cuối sẽ sieze lên khi tải thử nghiệm ứng dụng MVC dưới IIS đầy chất béo trên máy của nhà phát triển, kết nối với cụm dịch vụ dàn dựng.

Giải pháp: Đóng Channel

Sau khi thất bại trong việc hiểu được vấn đề và đọc nhiều, rất nhiều trang của MSDN và các nguồn khác khẳng định vấn đề không nên tồn tại ở tất cả, tôi đã cố gắng một lâu-shot và thay đổi Dispose() tôi làm việc xung quanh để ...

if (_client.State == CommunicationState.Faulted) _client.Abort(); 
else if (_client.State == CommunicationState.Opened) 
{ 
    ((IContextChannel)_client.Channel).Close(); 
    ((IDisposable)_client).Dispose(); 
} 
else ((IDisposable)_client).Dispose(); 

... và các vấn đề ngừng xảy ra trong tất cả các thiết lập kiểm tra và theo tải trọng trong môi trường chạy thử!

Tại sao?

Bất cứ ai có thể giải thích những gì có thể đã xảy ra và tại sao phải đóng một cách rõ ràng Channel trước khi gọi Dispose() giải quyết được không? Theo như tôi có thể nói, điều này không cần thiết.

Cuối cùng, tôi quay trở lại câu hỏi mở: Cách chính xác để Vứt bỏ một Proxy WCF là gì? Là sự thay thế của tôi cho Dispose() đầy đủ?

Trả lời

0

Vấn đề, theo như tôi có thể hiểu được, đó là việc gọi Dispose xử lý ra khỏi tay cầm, nhưng không thực sự đóng kênh, sau đó nắm giữ các tài nguyên và sau đó cuối cùng lần ra ngoài.

Đây là lý do tại sao dịch vụ của bạn ngừng phản hồi sau một thời gian trong quá trình kiểm tra tải: vì cuộc gọi ban đầu được giữ nguyên lâu hơn bạn tưởng và sau đó cuộc gọi có thể không tận dụng được các tài nguyên đó.

Tôi đã đưa ra giải pháp sau. Tiền đề của giải pháp là gọi Dispose là đủ để vứt bỏ tay cầm cũng như đóng kênh. Lợi ích bổ sung là nếu khách hàng kết thúc ở trạng thái bị lỗi, nó sẽ được tạo lại để các cuộc gọi tiếp theo thành công.

Nếu ServiceClient<TService> được tiêm vào một lớp khác thông qua khung tiêm phụ thuộc như Ninject, thì tất cả các tài nguyên sẽ được phát hành đúng cách.

NB: Xin lưu ý rằng trong trường hợp Ninject, ràng buộc phải xác định phạm vi, nghĩa là nó không được thiếu InXyzScope hoặc được xác định bằng InTransientScope. Nếu không có phạm vi nào có ý nghĩa, thì hãy sử dụng InCallScope.

Đây là những gì tôi đã đưa ra:

public class ServiceClient<TService> : IDisposable 
{ 
    private readonly ChannelFactory<TService> channelFactory; 

    private readonly Func<TService> createChannel; 

    private Lazy<TService> service; 

    public ServiceClient(ChannelFactory<TService> channelFactory) 
     : base() 
    { 
     this.channelFactory = channelFactory; 

     this.createChannel =() => 
     { 
      var channel = ChannelFactory.CreateChannel(); 

      return channel; 
     }; 

     this.service = new Lazy<TService>(() => CreateChannel()); 
    } 

    protected ChannelFactory<TService> ChannelFactory 
    { 
     get 
     { 
      return this.channelFactory; 
     } 
    } 

    protected Func<TService, bool> IsChannelFaulted 
    { 
     get 
     { 
      return (service) => 
       { 
        var channel = service as ICommunicationObject; 

        if (channel == null) 
        { 
         return false; 
        } 

        return channel.State == CommunicationState.Faulted; 
       }; 
     } 
    } 

    protected Func<TService> CreateChannel 
    { 
     get 
     { 
      return this.createChannel; 
     } 
    } 

    protected Action<TService> DisposeChannel 
    { 
     get 
     { 
      return (service) => 
       { 
        var channel = service as ICommunicationObject; 

        if (channel != null) 
        { 
         switch (channel.State) 
         { 
          case CommunicationState.Faulted: 
           channel.Abort(); 
           break; 

          case CommunicationState.Closed: 
           break; 

          default: 
           try 
           { 
            channel.Close(); 
           } 
           catch (CommunicationException) 
           { 
           } 
           catch (TimeoutException) 
           { 
           } 
           finally 
           { 
            if (channel.State != CommunicationState.Closed) 
            { 
             channel.Abort(); 
            } 
           } 
           break; 
         } 
        } 
       }; 
     } 
    } 

    protected Action<ChannelFactory<TService>> DisposeChannelFactory 
    { 
     get 
     { 
      return (channelFactory) => 
       { 
        var disposable = channelFactory as IDisposable; 

        if (disposable != null) 
        { 
         disposable.Dispose(); 
        } 
       }; 
     } 
    } 

    public void Dispose() 
    { 
     DisposeChannel(this.service.Value); 
     DisposeChannelFactory(this.channelFactory); 
    } 

    public TService Service 
    { 
     get 
     { 
      if (this.service.IsValueCreated && IsChannelFaulted(this.service.Value)) 
      { 
       DisposeChannel(this.service.Value); 

       this.service = new Lazy<TService>(() => CreateChannel()); 
      } 

      return this.service.Value; 
     } 
    } 
} 
Các vấn đề liên quan