Как сделать метод REST WCF полностью асинхронным с параллельной библиотекой задач?
Я пытаюсь сделать метод WCF REST полностью асинхронным (я не хочу ничего блокировать). По сути, у меня есть простой сервис с тремя слоями: Service, Business Logic и Data Access Layer. Уровень доступа к данным имеет доступ к базе данных, и для получения ответа от этого метода может потребоваться несколько секунд.
Я не очень хорошо понимаю, как цепочки всех этих методов работают. Может кто-то, пожалуйста, помогите мне заполнить образец, который я пытаюсь написать ниже? Я не очень хорошо понимаю шаблон, используемый 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
}
}
}
Ответы
Ответ 1
Вот пример. Я получил работу с помощью следующих сообщений:
Изменить: добавлен пример асинхронного клиента
Внедрение классического шаблона Async с использованием 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
Сначала вам нужно создать прокси-сервер async. Вы можете сделать это, щелкнув правой кнопкой мыши ссылку на службу (в папке "Ссылки" вашего проекта) и выбрав "Настроить сервисную ссылку". Установите флажок "Генерировать асинхронные операции".
Теперь у вашего клиентского прокси будут новые члены, которых раньше не было. Вот как их использовать:
// 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());
}
Ответ 2
вот реализация службы, которая реализует Async. В этом обратный вызов wcf передается до команды ado.net sql. Когда команда вернется, она вызовет метод 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;
}
}