Применение WCF

154

Фреймворк Windows Communication Foundation (WCF), появившийся в версии .NET 3.0, быстро стал стандартом де-факто организации сетевых взаимодействий в приложениях для .NET. Он поддерживает огромное множество сетевых протоколов и настроек, и непрерывно расширяется с каждой новой версией .NET. В этой статье рассказывается о приемах оптимизации фреймворка WCF.

Пороговые значения

Фреймворк WCF, в особенности до выхода версии .NET Framework 4.0, имел довольно консервативные пороговые значения. Основная их цель - обеспечить защиту от атак типа «отказ в обслуживании» (Denial of Service, DoS), но, к сожалению, в реальном мире они часто оказываются слишком строгими.

Настройки ограничений можно изменить, отредактировав раздел system.serviceModel либо в файле app.config (для обычных приложений), либо в файле web.config - для приложений ASP.NET:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceThrottling maxConcurrentCalls="16" 
              maxConcurrentSessions="10" maxConcurrentInstances="26" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Другой способ изменить эти параметры - настроить свойства объекта ServiceThrottle на этапе создания службы:

Uri baseAddress = new Uri("http://localhost:7556/Simple");
ServiceHost serviceHost = new ServiceHost(
    typeof(CalculatorService), baseAddress);

serviceHost.AddServiceEndpoint(
    typeof(ICalculator),
    new WSHttpBinding(),
    "CalculatorServiceObject");

serviceHost.Open();
IChannelListener icl = serviceHost.ChannelDispatchers[0].Listener;
ChannelDispatcher dispatcher = new ChannelDispatcher(icl);
ServiceThrottle throttle = dispatcher.ServiceThrottle;

throttle.MaxConcurrentSessions = 10;
throttle.MaxConcurrentCalls = 16;
throttle.MaxConcurrentInstances = 26;

Давайте разберемся, что означают все эти параметры:

MaxConcurrentSessions

Ограничивает количество сообщений, одновременно обрабатываемых узлом службы ServiceHost. При превышении этого предела сообщения будут помещаться в очередь. В .NET 3.5 по умолчанию используется значение 10, а в .NET 4 это значение получается, как произведение числа процессоров на 100.

MaxConcurrentCalls

Ограничивает количество объектов InstanceContext, обрабатываемых одновременно узлом службы ServiceHost. Запросы на создание дополнительных экземпляров помещаются в очередь и обрабатываются, когда количество объектов InstanceContext оказывается ниже указанного уровня. В .NET 3.5 по умолчанию используется значение 16, а в .NET 4 это значение получается, как произведение числа процессоров на 16.

MaxConcurrentInstances

Ограничивает количество сеансов, одновременно обслуживаемых узлом службы ServiceHost. Служба будет продолжать принимать соединения сверх указанного ограничения, но активными будут только каналы ниже этой границы (сообщения будут читаться из каналов). В .NET 3.5 по умолчанию используется значение 26, а в .NET 4 это значение получается, как произведение числа процессоров на 116.

Еще одно важное ограничение - количество параллельных соединений на каждый хост, которое по умолчанию равно двум. Если ваше приложение ASP.NET обращается к внешней службе WCF, это ограничение может оказаться узким местом. Ниже приводится пример конфигурации, где определяется это ограничение:

<system.net>
    <connectionManagement>
      <add address="*" maxconnection="100" />
    </connectionManagement>
</system.net>

Модель обработки

При разработке службы WCF вам потребуется определить ее модель активации и параллельной обработки. Сделать это можно с помощью свойств InstanceContextMode и ConcurrencyMode атрибута ServiceBehavior, соответственно. Свойство InstanceContextMode может принимать следующие значения:

Свойство ConcurrencyMode может принимать следующие значения:

Не используйте значения Single и Reentrant в свойстве ConcurrencyMode в сочетании со значением Single в свойстве InstanceContextMode. При использовании значения Multiple в свойстве ConcurrencyMode, используйте блокировки с высокой степенью детализации, чтобы уменьшить вероятность конкуренции между потоками.

Фреймворк WCF вызывает объекты службы из пула потоков завершения ввода/вывода, описанного ранее. Если в ходе обработки запросов понадобится выполнять синхронные операции ввода/вывода или ждать некоторого события, вам может потребоваться увеличить количество потоков, что можно сделать, отредактировав раздел system.web в конфигурационном файле приложения для ASP. NET или вызвав ThreadPool.SetMinThreads() и ThreadPool.SetMaxThreads() в обычном приложении.

<system.web>
      <processModel enable="true" autoConfig="false"
                    maxWorkerThreads="80" maxIoThreads="80"
                    minWorkerThreads="20" minIoThreads="20"/>
</system.web>

Кэширование в WCF

Фреймворк WCF не имеет встроенной поддержки кеширования. Даже если ваша служба на основе фреймворка WCF выполняется под управлением IIS, она все равно не сможет использовать его кеш по умолчанию. Чтобы включить поддержку кеширования, отметьте свой класс WCF-службы атрибутом AspNetCompatibilityRequirements:

[AspNetCompatibilityRequirements(
        RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed]

Кроме того, включите совместимость с ASP.NET, отредактировав файл web.config и добавив следующий элемент в раздел system.serviceModel:

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>

Начиная с версии .NET Framework 4.0 имеется возможность использовать новые типы System.Runtime.Caching для реализации поддержки кеширования. Они не зависят от сборки System.Web и поэтому могут использоваться не только в приложениях ASP.NET.

Асинхронные клиенты и серверы WCF

Фреймворк WCF позволяет выполнять асинхронные операции, как на стороне клиента, так и на стороне сервера. Каждая сторона может принимать независимое решение об использовании синхронных и асинхронных операций.

На стороне клиента поддерживается два способа асинхронного обращения к службе: на основе событий и с применением шаблона организации асинхронных взаимодействий в .NET. Модель на основе событий не совместима с каналами, созданными с помощью ChannelFactory. Чтобы получить возможность применить модель на основе событий, необходимо сгенерировать прокси-объект доступа к службе с помощью инструмента svcutil.exe, вызвав его с ключами /async и /tcv:Version35:

svcutil /n:http://Microsoft.ServiceModel.Samples, Microsoft.ServiceModel.Samples
http://localhost:7668/servicemodelsamples/service/mex /async /tcv:Version35

После этого прокси-объект можно использовать, как показано ниже:

// Асинхронные функции обратного вызова для отображения результатов
static void AddCallback(object sender, AddCompletedEventArgs e)
{
    Console.WriteLine("Add Result: {0}", e.Result);
}

static void Main(String[] args) 
{
    CalculatorClient client = new CalculatorClient();
    client.AddCompleted += new EventHandler<AddCompletedEventArgs>(AddCallback);
    client.AddAsync(100.0, 200.0);
}

В случае выбора модели на основе интерфейса IAsyncResult следует создать прокси-объект, вызвав svcutil.exe с ключом /async, но без ключа /tcv:version35. А затем реализовать функции обратного вызова и использовать методы Begin... прокси-объекта как показано ниже:

static void AddCallback(IAsyncResult ar)
{
    double result = ((CalculatorClient)ar.AsyncState).EndAdd(ar);
    Console.WriteLine("Добавлено: {0}", result);
}

static void Main(String[] args)
{
    ChannelFactory<ICalculatorChannel> factory = new ChannelFactory<ICalculatorChannel>();
    ICalculatorChannel channelClient = factory.CreateChannel();
    IAsyncResult arAdd = channelClient.BeginAdd(100.0, 200.0, AddCallback, channelClient);
}

На сервере асинхронный режим работы можно организовать за счет создания версий Begin... и End... контрактных операций. У вас не должно быть других операций с теми же именами, без префиксов Begin/End, потому что фреймворк WCF будет стремиться использовать их. Следуйте этим соглашениям об именовании, потому что в WCF они являются обязательными.

Метод Begin... должен принимать входные параметры и возвращать значение IAsyncResult, а сама операция ввода/вывода должна производиться асинхронно. Метод Begin... должен быть снабжен атрибутом OperationContract с параметром AsyncPattern, имеющим значение true.

Метод End... должен принимать параметр типа IAsyncResult, возвращать необходимое значение и иметь требуемые выходные параметры. Объект IAsyncResult (возвращаемый методом Begin...) должен содержать всю необходимую информацию о возвращаемом результате.

Кроме всего прочего, версия WCF 4.5 поддерживает новый шаблон async/await на основе класса Task для организации асинхронного ввода/вывода в серверных и клиентских приложениях. Например:

// Асинхронная служба на основе класса Task
public class StockQuoteService : IStockQuoteService
{
    async public Task<double> GetStockPrice(string stockSymbol)
    {
        double price = await FetchStockPriceFromDB();
        return price;
    }
}

// Асинхронный клиент на основе класса Task
public class TestServiceClient : 
    ClientBase<IStockQuoteService>, IStockQuoteService
{
    public Task<double> GetStockPriceAsync(string stockSymbol)
    {
        return Channel.GetStockPriceAsync();
    }
}

Привязки

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

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

Чтобы получить производительность, близкую к производительности привязки TCP, и при этом сохранить совместимость со средствами распределения нагрузки, можно использовать собственную привязку HTTP, реализующую обмен данными в двоичном формате. Ниже приводится пример настройки такой привязки:

<bindings>
    <customBinding>
      <binding name="NetHttpBinding">
        <binaryMessageEncoding />
        <httpTransport />
        <reliableSession />
        <compositeDuplex />
        <oneWay />
      </binding>
    </customBinding>
    <basicHttpBinding>
      <binding messageEncoding="Mtom" name="BasicMtom" />
    </basicHttpBinding>
    <wsHttpBinding>
      <binding name="NoSecurityBinding">
        <security mode="None" />
      </binding>
    </wsHttpBinding>
</bindings>
<services>
    <service name="MyServices.CalculatorService">
      <endpoint address=" " binding="customBinding" bindingConfiguration="NetHttpBinding"
      contract="MyServices.ICalculator" />
    </service>
</services>

Наконец, при возможности выбирайте простейшую привязку HTTP вместо WS-совместимой. Последняя использует не такой компактный формат сообщений.

Пройди тесты
Лучший чат для C# программистов