Подтвердить что ты не робот

Асинхронно потребляет синхронную службу WCF

Im в настоящее время находится в процессе переноса клиентского приложения на .NET 4.5, чтобы использовать async/await. Приложение является клиентом для службы WCF, которая в настоящее время предлагает только синхронные службы. Теперь мне интересно, , как я должен асинхронно использовать эту синхронную службу?

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

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

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

И в конце концов, поскольку сервер на самом деле не работает асинхронно (и, скорее всего, не будет для другого в то время), некоторые потоки всегда будут ждать: либо на клиенте, либо на сервере. И это также применяется при одновременном использовании сервисов (в настоящее время клиент ждет в фоновом потоке, чтобы пользовательский интерфейс реагировал).

Пример

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

Сервер предлагает синхронную услугу GetTest. Это тот, который в настоящее время существует, и где происходит работа - синхронно. Одним из вариантов было бы обернуть это в асинхронном методе, например, используя Task.Run, и предложить этот метод в качестве дополнительной услуги в контракте (требуя расширения интерфейса контракта).

// currently available, synchronous service
public string GetTest() {
    Thread.Sleep(2000);
    return "foo";
}

// possible asynchronous wrapper around existing service
public Task<string> GetTestAsync() {
    return Task.Run<string>(() => this.GetTest());
}

// ideal asynchronous service; not applicable as work is done synchronously
public async Task<string> GetTestRealAsync() {
    await Task.Delay(2000);
    return "foo";
}

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

В зависимости от того, какие методы теперь доступны, у меня есть два варианта:

  • Я могу асинхронно вызывать синхронную службу, обернув вызов:

    await Task.Run<string>(() => svc.GetTest());
    
  • Я могу асинхронно вызывать асинхронную службу напрямую, которая предоставляется сервером:

    await svc.GetTestAsync();
    

Оба работают нормально и не будут блокировать клиента. Оба метода включают ожидание на каком-то конце: Вариант 1 ждет клиента, что эквивалентно тому, что было сделано ранее в фоновом потоке. Вариант 2 ждет на сервере, обернув там синхронный метод.

Каким будет рекомендуемый способ сделать синхронную службу WCF async? Где я должен выполнять упаковку, на клиенте или на сервере? Или есть ли более эффективные варианты для этого, не ожидая чего-либо, т.е. Путем введения "реальной" асинхронности в соединении, например, сгенерированных прокси-серверов?

4b9b3361

Ответ 1

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

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

Итак, ваша сторона сервера будет выглядеть так:

using System.ServiceModel;
using System.Threading;

namespace WcfService
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string GetTest();
    }

    public class Service1 : IService
    {
        public string GetTest()
        {
            Thread.Sleep(2000);
            return "foo";
        }
    }
}

и ваша клиентская сторона будет выглядеть следующим образом

using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SandboxForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            var button = new Button();
            this.Controls.Add(button);

            button.Click += button_Click;
        }

        private async void button_Click(object sender, EventArgs e)
        {
            var factory = new ChannelFactory<IService>("SandboxForm.IService"); //Configured in app.config
            IService proxy = factory.CreateChannel();

            string result = await proxy.GetTestAsync();

            MessageBox.Show(result);
        }
    }

    [ServiceContract]
    public interface IService
    {
        [OperationContract(Action = "http://tempuri.org/IService/GetTest", ReplyAction = "http://tempuri.org/IService/GetTestResponse")]
        Task<string> GetTestAsync();
    }
}

Ответ 2

Если ваш API-интерфейс на стороне сервера может быть естественно асинхронным (например, ваш Task.Delay пример, а не Task.Run -сервер), объявите его как Task -ограниченный в интерфейсе контракта. В противном случае просто оставьте его синхронным (но не используйте Task.Run). Не создавайте несколько конечных точек для синхронных и асинхронных версий одного и того же метода.

Сгенерированный WSDL остается неизменным для асинхронных и синхронных API-интерфейсов, я только выяснил, что сам: Различные формы интерфейса контракта службы WCF. Ваши клиенты будут продолжать работать без изменений. Из-за того, что ваш серверный метод WCF асинхронен, все, что вы делаете, - это улучшение масштабируемости службы. Разумеется, это замечательная вещь, но упаковка синхронного метода с Task.Run скорее повредит масштабируемости, чем улучшит ее.

Теперь клиент вашей службы WCF не знает, реализован ли этот метод как синхронный или асинхронный на сервере, и ему не нужно это знать. Клиент может синхронно вызывать ваш метод (и блокировать поток клиента), или он может вызывать его асинхронно (без блокировки клиентского потока). В любом случае это не изменит тот факт, что ответное сообщение SOAP будет отправлено клиенту только, когда метод полностью завершится на сервере.

В ваш тестовый проект вы пытаетесь предоставить разные версии одного и того же API под разными названиями контрактов:

[ServiceContract]
public interface IExampleService
{
    [OperationContract(Name = "GetTest")]
    string GetTest();

    [OperationContract(Name = "GetTestAsync")]
    Task<string> GetTestAsync();

    [OperationContract(Name = "GetTestRealAsync")]
    Task<string> GetTestRealAsync();
}

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

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    Task<string> GetTestAsync(bool runSynchronously);
}

Затем в реализации вы могли бы сделать:

Task<string> GetTestAsync(bool runSynchronously)
{
    if (runSynchronously)
        return GetTest(); // or return GetTestAsyncImpl().Result;
    else
        return await GetTestAsyncImpl();
}

@usr подробно объясняет это здесь. Подводя итог, не похоже, что служба WCF возвращает ваш клиент, чтобы уведомить об завершении операции async. Скорее, он просто отправляет полный ответ SOAP, используя базовый сетевой протокол, когда это делается. Если вам нужно больше, вы можете использовать обратные вызовы WCF для любых уведомлений от сервера к клиенту, но это будет охватывать границы одного SOAP.

Ответ 3

Это не ужасно: fooobar.com/info/277024/.... Вы просто переносите возвращаемые значения с помощью Task.FromResult(). Вы должны изменить службу, но она по-прежнему синхронна, и вы не используете дополнительный поток. Это изменяет интерфейс на стороне сервера, который по-прежнему может использоваться совместно с клиентом, поэтому он может ждать асинхронно. В противном случае, похоже, что вы должны каким-то образом поддерживать отдельные контракты на сервере и клиенте.