Реализация службы async WCF

У меня есть приложение WPF, в котором я сейчас работаю над разделением на клиентскую и серверную стороны - с использованием WCF. Мне не нравился беспорядок, который я изначально получил с прямым решением, так что теперь я реструктурирую следующие рекомендации в скринкасте Мигеля Кастро, WCF Extreme. Если вы не знакомы с видео, он в основном настраивает все сообщение вручную - без использования ссылок на службы. Это включает в себя:

  • Общий договор со всеми контрактами на обслуживание и передачу данных, на которые ссылаются клиент и сервер
  • Консольное приложение, обслуживающее службу
  • Прокси-классы на клиенте, сопоставляющие службу и передающие ему вызовы (с использованием ClientBase или ClientFactory)

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

При добавлении ссылки на службу я могу установить флажок "Сгенерировать асинхронные операции", и я получаю MyServiceCompleted и MyServiceAsync. Тем не менее, я думаю, это то, что было создано при добавлении ссылки на службу, а не какая-то магия в классы, на которых это происходит?

Итак, могу ли я как-то получить асинхронные операции из ClientBase или ClientFactory? Или мне нужно определить, какие службы на стороне сервера будут асинхронными? Если да - может кто-нибудь, пожалуйста, дайте мне несколько советов или примеров о том, как начать работу с простой службой асинхронного обслуживания? Я читал в MSDN по этому вопросу, но это оставило меня все смущенное чувство, как идиот, чтобы не получить это уже.

Ответ 1

Реализация асинхронных операций на стороне сервера довольно проста. Убедитесь, что имена методов совпадают, и они начинаются с начала и конца. GetImageAsyncResult - это обычная реализация IAsyncResult (множество примеров в Интернете).

    public class MapProvider : IMapProvider //implementation - belongs to server
    {
         public IAsyncResult BeginGetImage(int level, int x, int y, string[] layers, AsyncCallback callback, object state)
         {
              GetImageAsyncResult asyncResult = new GetImageAsyncResult(level, x, y, layers, callback, state);
              ThreadPool.QueueUserWorkItem(Callback, asyncResult);
              return asyncResult;
         }

         private void Callback(object state)
         {

              GetImageAsyncResult asyncResult = state as GetImageAsyncResult;
              asyncResult.Image = TileProvider.GetImage(asyncResult.Level, asyncResult.X, asyncResult.Y, asyncResult.Layers);
              asyncResult.Complete();
         }

         public System.Drawing.Bitmap EndGetImage(IAsyncResult result)
         {
              using (GetImageAsyncResult asyncResult = result as GetImageAsyncResult)
              {
                   asyncResult.AsyncWait.WaitOne();
                   return asyncResult.Image;
              }
         }
    }

    public class MapProviderProxy : ClientBase<IMapProvider>, IMapProvider, IDisposable
    {
         public IAsyncResult BeginGetImage(int level, int x, int y, string[] layers, AsyncCallback callback, object state)
         {
              return Channel.BeginGetImage(level, x, y, layers, callback, state);
         }

         public System.Drawing.Bitmap EndGetImage(IAsyncResult result)
         {
              return Channel.EndGetImage(result);
         }

         public void Dispose()
         {
              if (State == CommunicationState.Faulted)
              {
                   Abort();
              }
              else
              {
                   try
                   {
                        Close();
                   }
                   catch
                   {
                        Abort();
                   }
              }
         }
    }

Ответ 2

Когда вы выбираете "Сгенерировать асинхронные операции", как вы отметили, магия не задействована: Begin... запустит ваше общение, сервер начнет обрабатывать материал, но вы не сможете ничего использовать, пока не назовете End....

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

Вероятно, вы думаете, что это должно быть сложным, но это не так;)

EDIT: Приведем пример:

using (Service.SampleClient client = new Service.SampleClient())
{
    client.AddCompleted += 
        (object sender, Service.AddCompletedEventArgs e)
            {
                Console.WriteLine(e.Result); // 2
            };
    client.AddAsync(1, 1);

    // wait for async callback
    Console.ReadLine();
}

[ServiceContract]
public interface ISample
{
    [OperationContract]
    int Add(int a, int b);
}

Вы также можете явно запрограммировать свою службу для работы async, как описано здесь: Синхронные и асинхронные операции, используя [OperationContract(AsyncPattern=true)]

Ответ 3

Альтернативный способ выполнения асинхронных операций на стороне клиента без использования svcutil - это настроить интерфейс (ServiceContract) локально на стороне клиента, который реализует операцию async.

Контракт на стороне сервера:

[ServiceContract]
public interface IServerContract
{
    [OperationContract]
    string GetData(int value);
 }

Асинхронный контракт на стороне клиента

[ServiceContract(Namespace = "http://tempuri.org/", Name = "IServerContract")]
 public interface IClientContractAsync
 {
      [OperationContract] 
      Task<string> GetDataAsync(int value);
 }

Обратите внимание, что имя и пространство имен по умолчанию должны быть установлены в клиентском контракте, чтобы соответствовать пространству имен контрактных серверов. Итак, теперь у вас есть асинхронная операция (без новых новых потоков). Таким образом, вам не нужно выполнять какую-либо конкретную реализацию на стороне сервера. Конечно, это похоже на то, что делает SvcUtil, но SvcUtil генерирует много дополнительного кода, и иногда я нахожу svcutil, вызывающий проблемы, например. с повторным использованием классов.

Клиентский прокси

public class ClientProxy : ClientBase<IClientContractAsync>
{
    public IClientContractAsync ChannelOut
    {
        get
        {
            return Channel;
        }
    }
}

Использование клиента:

static void Main(string[] args)
{
    var client = new ClientProxy();
    client.Open();
    var asyncReadValue = client.ChannelOut.GetDataAsync(45).Result;
    Console.WriteLine(asyncReadValue);
    Console.ReadLine();
}

Класс сервера

public class ServerService : IServerContract
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }
}

Хост сервера

static void Main(string[] args)
{
    var server = new ServiceHost(typeof(ServerService));
    server.Open();
    Console.WriteLine("started");
    Console.ReadLine();
}