Асинхронное начало/конец в том же методе

(я использую .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 выполнит.