Асинхронное начало/конец в том же методе
(я использую .Net 4.0)
Я хочу вызвать службу WCF асинхронно, с моего уровня обслуживания. Этот сервисный уровень используется контроллером MVC.Net. Я прочитал, что хорошая практика для вызова службы WCF асинхронно. Поэтому я использую begin/end (apm). Я хочу удвоить проверку, если я делаю это richt:
public byte[] GetSomeData()
{
IAsyncResult result = myServiceClient.BeginDoSomething(someInputValue, null, null);
var data = _pdfCreatieService.EndCreateForPreview(result);
return data;
}
Я не совсем уверен в коде выше, потому что я видел такие конструкции, как код ниже, который выглядит немного сложнее и ненужным в моем случае:
public byte[] GetSomeData()
{
var myState = new MyState();
IAsyncResult result = _myServiceClient.BeginDoSomething(someInputValue, CreateForPreviewCallback, myState);
result.AsyncWaitHandle.WaitOne();
return myState.Bytes;
}
private void DoSomethingCallback(IAsyncResult result)
{
var myState = (MyState)result.AsyncState;
myState.Bytes = _myServiceClient.EndDoSomething(result);
}
Спасибо Авнер Шахар-Каштан, Нед Стоянов и Носератио. Ваши ответы действительно проницательны!
Ответы
Ответ 1
Что ваш код будет делать, по сути, взять асинхронный метод и вызвать его синхронно. Когда вы вызываете метод EndDoSomething
, вы эффективно блокируете свой поток до тех пор, пока асинхронный метод не завершится, что в точности противоположно асинхронному вызову.
Конечно, ваш второй блок кода также вызывает код синхронно, блокируя выполнение явно, используя waithandle.
Что вы хотите сделать, вместо того, чтобы возвращать byte[]
из вашего первоначального метода, попросите DoSomethingCallback
сделать что-то активное с байтами - либо сохранить их в некотором классе, который может быть проверен контроллером, событие или сделать что-то еще. Если вы ждете своего асинхронного вызова, вы не получаете никакой пользы.
Что вы также можете сделать, если используете .NET 4.5 или выше (или .NET 4.0 в VS2012, используя BCL Async Package) заключается в использовании async/await
, который является хорошей оболочкой, которая позволит вам консолидировать метод вызова и метод обратного вызова в один, более когерентный метод.
Но независимо от выбранного вами синтаксиса или библиотек, первым шагом является понимание того, что асинхронное программирование обязательно нарушает поток управления кодом в вызове и обратный вызов результата или продолжение асинхронной операции.
Ответ 2
В ASP.NET MVC вы получаете выгоду только от асинхронных вызовов, если ваш контроллер также асинхронный.. По-видимому, это не относится к вашему коду, потому что вы блокируете с помощью WaitOne
внутри вашего метода контроллера.
Реализация асинхронных контроллеров очень просто с .NET 4.5, для получения дополнительной информации просмотрите "Использование асинхронных методов в ASP.NET MVC 4" .
С .NET 4.0 это немного более утомительно, проверьте "Использование асинхронного контроллера в ASP.NET MVC" . Ваш контроллер должен получить от AsyncController
и использовать AsyncManager
, чтобы уведомить стек ASP.NET об ожидающих асинхронных операциях.
Вот пример оттуда для .NET 4. 0, адаптированный для вашего дела (непроверенный). Обратите внимание на использование Task.Factory.FromAsync
и Task.ContinueWith
:
public static class WcfExt
{
public static Task<byte[]> DoSomethingAsync(
this IMyService service,
string someInputValue)
{
return Task.Factory.FromAsync(
(asyncCallback, asyncState) =>
service.BeginDoSomething(someInputValue, asyncCallback, asyncState),
(asyncResult) =>
service.EndDoSomething(asyncResult);
}
}
public class PortalController : AsyncController
{
public void NewsAsync(string someInputValue) {
AsyncManager.OutstandingOperations.Increment();
var myService = new MyService();
myService.DoSomethingAsync(someInputValue).ContinueWith((task) =>
{
AsyncManager.Parameters["data"] = task.Result;
AsyncManager.OutstandingOperations.Decrement();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
public ActionResult NewsCompleted(byte[] data)
{
return View("News", new ViewStringModel
{
NewsData = data
});
}
}
Ответ 3
Оба ваших подхода выполняют так называемую синхронизацию через async. Это выполняет синхронный асинхронный метод. Лучшим подходом было бы создание собственного асинхронного метода для перезаписи данных с помощью TaskCompletionSource
. Я не тестировал это, но вы должны сделать что-то вроде этого:
public Task<byte[]> GetSomeDataAsync()
{
var tcs = new TaskCompletionSource();
IAsyncResult result = myServiceClient.BeginDoSomething(someInputValue, x =>
{
try
{
var data = _pdfCreatieService.EndCreateForPreview(result);
tcs.SetResult(data);
}
catch(Exception ex)
{
tcs.SetException(ex);
}
}, null);
return tcs.Task;
}
Тогда для его использования просто сделайте это
GetSomeDataAsync().ContinueWith(t => /*<Use retrieved data from t.Result>*/);
Этот код вернется сразу, и как только асинхронная операция будет завершена, часть ContinueWith
выполнит.