Как сделать метод WCF REST полностью асинхронным с библиотекой параллельных задач? - PullRequest
21 голосов
/ 07 ноября 2011

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

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

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

using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Threading.Tasks;

namespace WcfRestService1
{
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = 
        AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Service1
    {
        private BusinessLogic bll = new BusinessLogic();

        // Synchronous version
        [WebGet(UriTemplate = "/sync")]
        public string GetSamples()
        {
            return bll.ComputeData();
        }

        // Asynchronous version - Begin
        [WebGet(UriTemplate = "/async")]
        [OperationContract(AsyncPattern = true)]
        public IAsyncResult BeginGetSampleAsync(AsyncCallback callback, 
            object state)
        {
            Task<string> t = bll.ComputeDataAsync();

            // What am I suppose to return here
            // return t.AsyncState; ???
        }

        // Asynchronous version - End
        public List<SampleItem> EndGetSampleAsync(IAsyncResult result)
        {
            // How do I handle the callback here?
        }
    }

    public class BusinessLogic
    {
        public Task<string> ComputeDataAsync()
        {
            DataAccessLayer dal = new DataAccessLayer();
            return dal.GetData();
        }

        public string ComputeData()
        {
            Task<string> t = this.ComputeDataAsync();

            // I am blocking... Waiting for the data
            t.Wait();

            return t.Result;
        }
    }

    public class DataAccessLayer
    {
        public Task<string> GetData()
        {
            // Read data from disk or network or db
        }
    }
}

Ответы [ 2 ]

11 голосов
/ 29 ноября 2011

Вот пример. Я получил его с помощью следующих сообщений:

Редактировать: добавлен пример асинхронного клиента

Реализация классического асинхронного шаблона с использованием TPL
http://pfelix.wordpress.com/2008/06/27/wcf-and-the-asyncpattern-property-part-1/ http://pfelix.wordpress.com/2008/06/28/wcf-and-the-asyncpattern-part-2/

Вот небольшой сервис, который ничего не делает:


namespace WcfAsyncTest
{
    [ServiceContract]
    public interface IAsyncTest
    {
        [OperationContract(AsyncPattern=true)]
        IAsyncResult BeginOperation(AsyncCallback callback, object state);

        string EndOperation(IAsyncResult ar);
    }

    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Service1 : IAsyncTest
    {
        public IAsyncResult BeginOperation(AsyncCallback callback, object state)
        {
            Task result = Task.Factory.StartNew((x) =>
                {
                    // spin to simulate some work
                    var stop = DateTime.Now.AddSeconds(10);
                    while (DateTime.Now < stop)
                        Thread.Sleep(100);
                }, state);
            if (callback != null)
                result.ContinueWith(t => callback(t));
            return result;
        }

        public string EndOperation(IAsyncResult ar)
        {
            ar.AsyncWaitHandle.WaitOne();
            return "Hello!!";
        }
    }
}


А вот клиент (командная строка):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var client = new ServiceReference1.AsyncTestClient();
            var result = client.Operation();
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }
}

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

Пример Async Client

Сначала вам нужно будет создать асинхронный прокси. Это можно сделать, щелкнув правой кнопкой мыши Справочник по услугам (в папке «Ссылки» вашего проекта) и выбрав «Настроить справочник по услугам». Установите флажок «Создать асинхронные операции».

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


// this is in the command-line test client
// no changes to your service required.
static void AsyncTest()
{
  var client = new ServiceReference1.AsyncTestClient();
  client.OperationCompleted += new EventHandler(client_OperationCompleted);
  client.OperationAsync();
  Console.WriteLine("Operation Running");
}

static void client_OperationCompleted(object sender, ServiceReference1.OperationCompletedEventArgs e)
{
  if (e.Error == null)
    Console.WriteLine("Operation Complete.  Result: " + e.Result);
  else
    Console.WriteLine(e.Error.ToString());
}


3 голосов
/ 29 ноября 2011

здесь - реализация службы, которая реализует Async.В этом обратный вызов wcf полностью передается команде sql ado.net.Когда команда вернется, она вызовет метод EndXXX службы, который вызовет бизнес-уровень, который в конечном итоге вызовет EndXXX из SqlCommand.Дайте мне знать, если у вас возникнут проблемы

public class Service
    {
        private BusinessLogic businessLayer = new BusinessLogic();
        public IAsyncResult BeginAnyOperation(AsyncCallback callback, object userState)
        {
            return businessLayer.BeginComputeData(callback, userState);
        }
        public string EndAnyOperation(IAsyncResult result)
        {
            return businessLayer.EndComputeDate(result);
        }
    }

    public class MyState<T> : IAsyncResult
    {
        public MyState() { }
        public object AsyncState { get; set; }
        public WaitHandle AsyncWaitHandle { get; set; }
        public bool CompletedSynchronously
        {
            get { return true; }
        }
        public bool IsCompleted { get; set; }
        public AsyncCallback AsyncCallback { get; set; }
        public T Result { get; set; }
        public IAsyncResult InnerResult { get; set; }
    }

    public class BusinessLogic
    {
        private DataAccessLayer dal = new DataAccessLayer();
        public IAsyncResult BeginComputeData(AsyncCallback callback, object state)
        {
            return dal.BeginGetData(callback, state);
        }
        public string EndComputeDate(IAsyncResult asyncResult)
        {
            return dal.EndGetData(asyncResult);
        }
    }

    public class DataAccessLayer
    {
        public IAsyncResult BeginGetData(AsyncCallback callback, object state)
        {
            var conn = new SqlConnection("");
            conn.Open();
            SqlCommand cmd = new SqlCommand("myProc", conn);
            var commandResult = cmd.BeginExecuteReader(callback, state, System.Data.CommandBehavior.CloseConnection);
            return new MyState<string> { AsyncState = cmd, InnerResult = commandResult };
        }
        public string EndGetData(IAsyncResult result)
        {
            var state = (MyState<string>)result;
            var command = (SqlCommand)state.AsyncState;
            var reader = command.EndExecuteReader(state.InnerResult);
            if (reader.Read())
                return reader.GetString(0);
            return string.Empty;
        }
    }
...