Как лучше всего обойти проблему с клиентом WCF, использующим блокировку?

377

Мне нравится создавать экземпляры моих клиентов службы 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 без опасности маскировки исключения с ошибкой.

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

  • 39
    Последний (который проверяет это. Государство) - это гонка; он может не быть ошибочным при проверке логического значения, но может быть ошибочным при вызове метода Close ().
  • 14
    Вы читаете состояние; это не вина. Перед вызовом Close () канал выходит из строя. Закрыть () бросает. Игра окончена.
Показать ещё 8 комментариев
Теги:
wcf
wcf-client
using

26 ответов

133
Лучший ответ

На самом деле, хотя я размещен в блоге (см. Luke answer), я думаю, this лучше, чем мой IDisposable wrapper. Типичный код:

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

(отредактировать для комментариев)

Так как Use возвращает void, самый простой способ обработки возвращаемых значений - через захваченную переменную:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
  • 2
    @MarcGravell Где я могу ввести этот клиент? Я предполагаю, что ChannelFactory создает клиента, и объект фабрики обновляется внутри класса Service, что означает, что код должен быть немного реорганизован, чтобы разрешить собственную фабрику. Это правильно, или я что-то упускаю здесь очевидное?
  • 15
    Вы можете легко изменить оболочку, чтобы вам не понадобилась переменная захвата для результата. Примерно так: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
Показать ещё 8 комментариев
88

Учитывая выбор между решением, защищенным IServiceOriented.com, и решением, защищенным блогом Дэвида Баррета, я предпочитаю простоту, предлагаемую путем переопределения метод Dispose() клиента. Это позволяет мне продолжать использовать оператор using(), как можно было бы ожидать с одноразовым объектом. Однако, как отметил @Brian, это решение содержит условие гонки, в котором государство не может быть ошибочно проверено, когда оно проверено, но может быть к тому времени, когда вызывается Close(), и в этом случае CommunicationException все еще происходит.

Итак, чтобы обойти это, я использовал решение, которое смешивает лучшее из обоих миров.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
  • 2
    не рискованно ли использовать оператор Try-finally (или синтаксический сахар - оператор using () {} ") с неуправляемыми ресурсами? В данном случае, если опция «Закрыть» не срабатывает, исключение не перехватывается и, наконец, может не работать. Кроме того, если в операторе finally есть исключение, оно может маскировать другие исключения. Я думаю, именно поэтому Try-Catch предпочтительнее.
  • 0
    Зак, не ясно, на вашем объекте; что мне не хватает? Если метод Close генерирует исключение, блок finally будет выполняться до того, как будет сгенерировано исключение. Правильно?
Показать ещё 4 комментария
27

Я написал функцию более высокий порядок, чтобы она работала правильно. Мы использовали это в нескольких проектах, и, похоже, он отлично работает. Так должно было быть сделано с самого начала, без "использования" парадигмы или так далее.

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

Вы можете делать такие вызовы:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

Это почти так же, как в вашем примере. В некоторых проектах мы пишем строго типизированные вспомогательные методы, поэтому в итоге мы записываем такие вещи, как "Wcf.UseFooService(f = > f...)".

Я считаю это довольно элегантным, все рассмотрено. Есть ли определенная проблема, с которой вы столкнулись?

Это позволяет подключать другие отличные функции. Например, на одном сайте сайт аутентифицируется службой от имени зарегистрированного пользователя. (У сайта нет собственных учетных данных.) Написав наш собственный помощник метода "UseService", мы можем настроить канал factory так, как мы хотим, и т.д. Мы также не обязаны использовать сгенерированные прокси-серверы - любой интерфейс сделаю.

  • 0
    Я получаю исключение: свойство Address на ChannelFactory.Endpoint было нулевым. Конечная точка ChannelFactory должна иметь правильный указанный адрес . Что GetCachedFactory метод GetCachedFactory ?
25

Это рекомендуемый Microsoft способ обработки вызовов клиентов WCF:

Подробнее см. Ожидаемые исключения

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

Дополнительная информация Так много людей, похоже, задают этот вопрос в WCF, что Microsoft даже создала специальный образец, чтобы продемонстрировать, как обрабатывать исключения:

C:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\клиент

Загрузите образец: С# или VB

Учитывая, что существует так много проблем с использованием оператора using, (нагретый?) Внутренние обсуждения и темы по этой проблеме, я не собираюсь тратить время на то, чтобы стать ковбой кода и найти более чистый способ. Я просто высасываю его и внедряю WCF-клиентам этот подробный (но надежный) способ для моих серверных приложений.

Дополнительные дополнительные сбои для

Множество исключений вытекает из CommunicationException, и я не думаю, что большинство этих исключений следует повторить. Я провалился через каждое исключение в MSDN и нашел короткий список исключений, доступных для повторной попытки (в дополнение к TimeOutException выше). Дайте мне знать, если я пропустил исключение, которое следует повторить.

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

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

  • 1
    Код из примера все еще вызывает проблемы? Я попытался запустить проект UsingUsing (VS2013), но строка "Hope this code wasn't important, because it might not happen." все еще выполняется ...
14

Наконец-то я нашел твердые шаги в направлении чистого решения этой проблемы.

Этот настраиваемый инструмент расширяет WCFProxyGenerator для предоставления прокси-сервера обработки исключений. Он генерирует дополнительный прокси, называемый ExceptionHandlingProxy<T>, который наследует ExceptionHandlingProxyBase<T>, последний из которых реализует мясо прокси-функциональности. В результате вы можете использовать прокси-сервер по умолчанию, который наследует ClientBase<T> или ExceptionHandlingProxy<T>, который инкапсулирует управление временем жизни канала factory и канала. ExceptionHandlingProxy соответствует вашим выборам в диалоговом окне "Добавить служебную ссылку" в отношении асинхронных методов и типов коллекций.

Codeplex имеет проект с именем Обработка исключений с использованием прокси-сервера WCF. Он в основном устанавливает новый настраиваемый инструмент для Visual Studio 2008, затем используйте этот инструмент для создания нового прокси-сервера службы (добавьте ссылку на службу). У этого есть хорошая функциональность, чтобы справиться с неисправными каналами, тайм-аутами и безопасным удалением. Там отличное видео здесь называется ExceptionHandlingProxyWrapper, объясняющее, как это работает.

Вы можете безопасно использовать оператор Using еще раз, и если канал поврежден при любом запросе (TimeoutException или CommunicationException), Wrapper будет повторно инициализировать неисправный канал и повторить запрос. Если это не удастся, он вызовет команду Abort() и удалит прокси-сервер и восстановит исключение. Если служба выдает код FaultException, он прекратит выполнение, и прокси будет прерван безопасным образом выбрасывать правильное исключение, как ожидалось.

  • 0
    @Kiquenet wcfproxygenerator.codeplex.com
  • 0
    @Shimmy Status Beta. Дата: суббота, 11 июля 2009 года, Мишель Бустаманте . Мертвый проект?
10

Основываясь на ответах Марка Гравелла, Майкла Гага и Мэтта Дэвиса, наши разработчики пришли к следующему:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

Пример использования:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

Это как можно ближе к синтаксису "using", вам не нужно возвращать фиктивное значение при вызове метода void, и вы можете совершать несколько вызовов для службы (и возвращать несколько значений) без необходимости использовать кортежи.

Кроме того, вы можете использовать это с ClientBase<T> потомками вместо ChannelFactory, если хотите.

Метод расширения раскрывается, если разработчик хочет вручную удалить прокси-канал.

  • 0
    Использование этого имеет смысл, если я использую PoolingDuplex и не закрываю соединение после вызова, чтобы моя клиентская служба могла жить даже несколько дней и обрабатывать обратные вызовы сервера. Насколько я понимаю, решение, которое обсуждается здесь, имеет смысл для одного вызова за сеанс?
  • 0
    @sll - это для закрытия соединения сразу после возврата вызова (один вызов за сеанс).
Показать ещё 5 комментариев
8

@Marc Gravell

Нельзя ли использовать это:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

Или, то же самое (Func<T, TResult>) в случае Service<IOrderService>.Use

Это упростит возвращаемые переменные.

  • 1
    +1 @MarcGravell Я думаю, что ваш ответ «мог бы быть лучше»: P (и действие можно реализовать в терминах Func с нулевым возвратом). Вся эта страница - беспорядок - я бы сформулировал единую и прокомментировал бы ошибки, если бы я планировал использовать WCF в любое время в этом десятилетии ...
7

Что это?

Это CW-версия принятого ответа, но с (что я считаю полным). Обработка исключений включена.

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

Простое использование клиента WCF

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

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

ServiceDelegate.cs

Добавьте этот файл в свое решение. Для этого файла не требуются изменения, если вы не хотите изменять количество попыток или какие исключения вы хотите обрабатывать.

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;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: Я сделал это сообщение в сообществе wiki. Я не буду собирать "очки" из этого ответа, но предпочитаю, чтобы вы повысили его, если вы согласны с реализацией или отредактируете его, чтобы сделать его лучше.

  • 0
    Я не уверен, что согласен с вашей характеристикой этого ответа. Это версия CW с добавленной вами идеей обработки исключений .
  • 0
    @JohnSaunders - True (моя концепция обработки исключений). Дайте мне знать о любых исключениях, которые я пропускаю или неправильно обращаюсь.
Показать ещё 5 комментариев
7

Ниже приведена расширенная версия источника из вопроса и расширена для кэширования многоканальных фабрик и попытка поиска конечной точки в файле конфигурации по имени контракта.

Он использует .NET 4 (в частности: контравариантность, LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}
  • 0
    Какую выгоду в производительности дает это предложение?
  • 1
    Зачем использовать UseServiceDelegate<T> вместо Action<T> ?
Показать ещё 1 комментарий
5

Обертка, подобная этой, будет работать:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

Это должно позволить вам написать код, например:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

Обертка, конечно, может занять больше исключений, если это необходимо, но принцип остается тем же.

  • 0
    Я помню дискуссию о том, что Dispose не вызывается при определенных условиях ... что приводит к утечке памяти с WCF.
  • 0
    Я не уверен, что это привело к утечке памяти, но проблема в этом. Когда вы вызываете Dispose для IChannel, он может выдать исключение, если канал находится в состоянии сбоя, это проблема, так как Microsoft указывает, что Dispose никогда не должен выдавать. Так что код выше обрабатывает случай, когда Close вызывает исключение. Если Abort бросает, это может быть что-то серьезно не так. Я написал сообщение в блоге об этом в декабре прошлого года: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
4

Я использовал динамический прокси Castle для решения проблемы Dispose(), а также реализовал автоматическое обновление канала, когда он находится в непригодном для использования состоянии. Чтобы использовать это, вы должны создать новый интерфейс, который наследует ваш контракт на обслуживание и IDisposable. Динамический прокси-сервер реализует этот интерфейс и обертывает канал WCF:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

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

Посмотрите на код, на самом деле это довольно просто: Динамический прокси WCF

4

Если вам не нужен IoC или используется автогенерированный клиент (Service Reference), тогда вы можете просто использовать оболочку для управления закрытие и GC взять клиентскую базу, когда она находится в безопасном состоянии, которое не будет вызывать никаких исключений. GC вызовет Dispose в serviceclient, и это вызовет Close. Так как он закрыт, он не может нанести никакого ущерба. Я использую это без проблем в производственном коде.

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

Затем, когда вы обращаетесь к серверу, вы создаете клиента и используете using в autodisconect:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}
3

Резюме

Используя методы, описанные в этом ответе, можно использовать службу WCF в блоке using со следующим синтаксисом:

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

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


Подробнее

Все ответы, приведенные до сих пор, касаются проблемы обхода "ошибки" в реализации канала WCF IDisposable. Ответ, который, кажется, предлагает самую краткую модель программирования (позволяющую вам использовать блок using для размещения на неуправляемых ресурсах), этот, где прокси-сервер модифицирован для реализации IDisposable с безаварийной реализацией. Проблема с этим подходом заключается в ремонтопригодности - мы должны повторно внедрить эту функциональность для использования прокси-сервера, который мы используем. По разновидности этого ответа мы увидим, как мы можем использовать композицию, а не наследование, чтобы сделать эту технику общей.

Первая попытка

Кажется, что для реализации IDisposable существуют различные реализации, но для аргумента мы будем использовать адаптацию, используемую принятым в настоящее время ответом.

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

Вооружившись указанными выше классами, мы можем теперь написать

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

Это позволяет нам использовать нашу службу, используя блок using:

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Создание этого общего

Все, что мы сделали до сих пор, это переформулировать решение Tomas. Что предотвращает генерацию этого кода, так это то, что класс ProxyWrapper должен быть повторно реализован для каждого требуемого контракта. Теперь мы рассмотрим класс, который позволяет нам динамически создавать этот тип с помощью IL:

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

С нашим новым вспомогательным классом мы можем теперь написать

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Обратите внимание, что вы также можете использовать ту же технику (с небольшими изменениями) для автоматически сгенерированных клиентов, наследующих для ClientBase<> (вместо использования ChannelFactory<>), или если вы хотите использовать другую реализацию IDisposable для закройте свой канал.

3

Используйте метод расширения:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}
2

Мне нравится этот способ закрытия соединения:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
1

Я хотел бы добавить реализацию сервиса из ответа Марка Гравелла для случая использования ServiceClient вместо ChannelFactory.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}
1

Для желающих, здесь перевод VB.NET принятого ответа (см. ниже). Я немного уточнил это для краткости, объединив некоторые советы других в этой теме.

Я признаю это вне темы для исходных тегов (С#), но поскольку я не смог найти версию этого прекрасного решения VB.NET, я предполагаю, что другие будут выглядеть также. Перевод Lambda может быть немного сложным, поэтому я хотел бы сэкономить кому-то неприятности.

Обратите внимание, что эта конкретная реализация обеспечивает возможность настройки ServiceEndpoint во время выполнения.


Код:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

Применение:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property
1
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

Таким образом, он позволяет хорошо писать операторы возврата:

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

Я написал простой базовый класс, который обрабатывает это. Он доступен как пакет NuGet, и он довольно прост в использовании.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}
  • 0
    Какие-либо обновления для VS2013-.net 4.5.1? какие-либо варианты для Retry, как stackoverflow.com/a/9370880/206730 ? -
  • 0
    @Kiquenet Я больше не работаю над WCF. Если вы отправите мне запрос на извлечение, я могу объединить его и обновить пакет.
1

В нашей системной архитектуре часто используется Unity IoC для создания экземпляров ClientBase, поэтому нет уверенности в том, что другие разработчики даже используют блоки using{}. Чтобы сделать это как можно более безупречным, я создал этот пользовательский класс, который расширяет ClientBase и обрабатывает закрытие канала при удалении или завершение в случае, если кто-то явно не уничтожит созданный Unity экземпляр.

Существует также материал, который необходимо сделать в конструкторе, чтобы настроить канал для пользовательских учетных данных и прочее, так что здесь тоже...

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Тогда клиент может просто:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

И вызывающий может выполнить любой из этих действий:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}
  • 0
    Вы никогда не используете удаление параметров в вашем методе Dispose
  • 0
    @Chad - я следовал общему шаблону Microsoft « Завершение / удаление» : msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Это правда, что я не использую переменную, потому что я не не нужно делать какую-то другую очистку между обычным избавлением и финализацией. Можно было бы переписать, чтобы просто вызвать Finalize для вызова Dispose () и переместить код из Dispose (bool) в Dispose ().
Показать ещё 1 комментарий
0

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

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..
        }
    }
}

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

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

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

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

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

0

Переопределите клиента 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
    добавить информацию, что нового в вашем ответе?
0

Следующий помощник позволяет вызывать методы void и non-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

У меня есть собственная оболочка для канала, который реализует 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

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

Мне нужно было что-то сделать с клиентом 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));
  • 0
    Как насчет конструктора клиента, использующего привязку и конечную точку? TClient (связывание, конечная точка)
-3

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню