Как лучше всего обойти проблему с клиентом WCF, использующим блокировку? - PullRequest
393 голосов
/ 22 февраля 2009

Мне нравится создавать экземпляры моих клиентов службы WCF в блоке using, поскольку это в значительной степени стандартный способ использования ресурсов, реализующих IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Но, как отмечалось в этой статье MSDN , упаковка клиента WCF в блок using может маскировать любые ошибки, которые приводят к тому, что клиент остается в неисправном состоянии (например, тайм-аут или проблема со связью). ). Короче говоря, когда вызывается Dispose (), клиентский метод Close () запускается, но выдает ошибку, потому что он находится в неисправном состоянии. Исходное исключение затем маскируется вторым исключением. Не хорошо.

Предложенный обходной путь в статье MSDN состоит в том, чтобы полностью избежать использования блока using, а вместо этого создавать экземпляры ваших клиентов и использовать их примерно так:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

По сравнению с блоком using, я думаю, это ужасно. И много кода, чтобы писать каждый раз, когда вам нужен клиент.

К счастью, я нашел несколько других обходных путей, таких как этот, в IServiceOriented. Вы начинаете с:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Что позволяет:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Это неплохо, но я не думаю, что это так выразительно и легко понятно, как блок using.

Обходной путь, который я сейчас пытаюсь использовать, о котором я впервые прочитал на blog.davidbarret.net . По сути, вы переопределяете метод клиента Dispose() везде, где вы его используете. Что-то вроде:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Похоже, что можно снова разрешить блок using без опасности замаскировать исключение состояния ошибки.

Итак, есть ли какие-то другие ошибки, которые я должен обратить внимание на использование этих обходных путей? Кто-нибудь придумал что-нибудь лучше?

Ответы [ 26 ]

0 голосов
/ 23 декабря 2014

Следующий помощник позволяет вызывать void и не пустые методы. Использование:

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

Сам класс:

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}
0 голосов
/ 15 января 2014

У меня есть своя оболочка для канала, которая реализует Dispose следующим образом:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

Это, кажется, работает хорошо и позволяет использовать блок использования.

0 голосов
/ 03 сентября 2015

Переопределите клиентский Dispose () без необходимости генерировать прокси-класс на основе ClientBase, также без необходимости управлять созданием канала и кэшированием ! (Обратите внимание, что WcfClient не является классом ABSTRACT и основан на ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}
0 голосов
/ 23 мая 2013

Вы также можете использовать DynamicProxy для расширения метода Dispose(). Таким образом, вы можете сделать что-то вроде:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
0 голосов
/ 28 января 2016

Мой метод - создать унаследованный класс, который явно реализует IDisposable. Это полезно для людей, которые используют графический интерфейс для добавления ссылки на сервис (Add Service Reference). Я просто добавляю этот класс в проект, создавая ссылку на службу, и использую ее вместо клиента по умолчанию:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

Примечание: это просто простая реализация dispose, вы можете реализовать более сложную логику dispose, если хотите.

Затем вы можете заменить все ваши звонки, сделанные обычным сервисным клиентом, на безопасные клиенты, например:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

Мне нравится это решение, так как оно не требует от меня доступа к определениям интерфейса, и я могу использовать оператор using, как и следовало ожидать, позволяя моему коду выглядеть более или менее одинаково.

Вам все равно нужно будет обработать исключения, которые могут быть выданы, как указано в других комментариях в этой теме.

0 голосов
/ 16 августа 2013

Я сослался на несколько ответов на этот пост и настроил его в соответствии со своими потребностями.

Я хотел иметь возможность что-то сделать с клиентом WCF, прежде чем использовать его, поэтому DoSomethingWithClient() метод.

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

Вот класс помощников:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

И я могу использовать его как:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
...