Подтвердить что ты не робот

Ловушки (Mis) с использованием итераторов С# для реализации Corouts

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

Как следствие, писать то, что когда-то было просто, становится все более сложным (см. примеры кода в конце моего вопроса).

В идеале я бы использовал сопрограммы, чтобы упростить реализацию, но, к сожалению, С# в настоящее время не поддерживает сопрограммы как средство для родного языка. Однако у С# есть понятие генераторов (итераторов) с использованием синтаксиса yield return. Моя идея состоит в том, чтобы повторно назначить ключевое слово yield, чтобы я мог создать простую модель coroutine для той же логики.

Я не хочу этого делать, потому что я беспокоюсь, что могут быть некоторые скрытые (технические) подводные камни, которые я не ожидаю (учитывая мою относительную неопытность с Silverlight и WCF). Я также обеспокоен тем, что механизм реализации может быть непонятен будущим разработчикам и может помешать, а не упростить их усилия по поддержанию или расширению кода в будущем. Я видел этот вопрос на SO о повторном назначении итераторов для создания государственных машин: реализация конечного автомата с использованием" yield " ключевое слово, и, хотя это не то же самое, что я делаю, это заставляет меня замолчать.

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

Оригинальная версия кода, отличная от WCF, выглядит примерно так:

void Button_Clicked( object sender, EventArgs e ) {
   using( var bizLogic = new BusinessLogicLayer() ) {
       try  {
           var resultFoo = bizLogic.Foo();
           // ... do something with resultFoo and the UI
           var resultBar = bizLogic.Bar(resultFoo);
           // ... do something with resultBar and the UI
           var resultBaz = bizLogic.Baz(resultBar);
           // ... do something with resultFoo, resultBar, resultBaz
       }
   }
}

Рефакторизованная версия WCF становится немного более сложной (даже без обработки исключений и тестирования перед/после состояния):

// fields needed to manage distributed/async state
private FooResponse m_ResultFoo;  
private BarResponse m_ResultBar;
private BazResponse m_ResultBaz;
private SomeServiceClient m_Service;

void Button_Clicked( object sender, EventArgs e ) {
    this.IsEnabled = false; // disable the UI while processing async WECF call chain
    m_Service = new SomeServiceClient();
    m_Service.FooCompleted += OnFooCompleted;
    m_Service.BeginFoo();
}

// called asynchronously by SL when service responds
void OnFooCompleted( FooResponse fr ) {
    m_ResultFoo = fr.Response;
    // do some UI processing with resultFoo
    m_Service.BarCompleted += OnBarCompleted;
    m_Service.BeginBar();
}

void OnBarCompleted( BarResponse br ) {
    m_ResultBar = br.Response;
    // do some processing with resultBar
    m_Service.BazCompleted += OnBazCompleted;
    m_Service.BeginBaz();
} 

void OnBazCompleted( BazResponse bz ) {
    m_ResultBaz = bz.Response;
    // ... do some processing with Foo/Bar/Baz results
    m_Service.Dispose();
}

Вышеприведенный код, очевидно, является упрощением, поскольку он исключает обработку исключений, проверки недействительности и другие методы, которые необходимы в производственном коде. Тем не менее, я думаю, что это демонстрирует быстрое увеличение сложности, которое начинается с асинхронной модели программирования WCF в Silverlight. Повторная факторизация первоначальной реализации (которая не использовала служебный уровень, а скорее имела свою логику, встроенную в SL-клиент) быстро выглядит сложной задачей. И тот, который, вероятно, будет подвержен ошибкам.

Совлокальный вариант кода будет выглядеть примерно так (я еще не тестировал это):

void Button_Clicked( object sender, EventArgs e ) {
    PerformSteps( ButtonClickCoRoutine );
}

private IEnumerable<Action> ButtonClickCoRoutine() {
    using( var service = new SomeServiceClient() ) {
        FooResponse resultFoo;
        BarResponse resultBar;
        BazResponse resultBaz;

        yield return () => {
            service.FooCompleted = r => NextStep( r, out resultFoo );
            service.BeginFoo();
        };
        yield return () => {
            // do some UI stuff with resultFoo
            service.BarCompleted = r => NextStep( r, out resultBar );
            service.BeginBar();
        };
        yield return () => {
            // do some UI stuff with resultBar
            service.BazCompleted = r => NextStep( r, out resultBaz );
            service.BeginBaz();
        };
        yield return () => {
            // do some processing with resultFoo, resultBar, resultBaz
        }
    }
}

private void NextStep<T>( T result, out T store ) { 
    store = result;
    PerformSteps();  // continues iterating steps
}

private IEnumerable<Action> m_StepsToPerform;
private void PerformSteps( IEnumerable<Action> steps ) {
   m_StepsToPerform = steps;
   PerformSteps();        
}

private void PerformSteps() {
   if( m_StepsToPerform == null ) 
       return; // nothing to do

   m_StepsToPerform.MoveNext();
   var nextStep = m_StepsToPerform.Current;
   if( nextStep == null ) {
       m_StepsToPerform.Dispose();
       m_StepsToPerform = null;
       return; // end of steps
   }
   nextStep();
}

В приведенном выше коде есть всевозможные вещи, которые необходимо улучшить. Но основная предпосылка заключается в том, чтобы разделить шаблон продолжения (создание точки перехвата для обработки исключений и различных проверок), позволяя при этом использовать асинхронную модель WCF на основе событий, когда каждый шаг выполняется - в основном, когда завершается последний вызов async WCF. Хотя на первый взгляд это выглядит как больше кода, стоит упомянуть, что PerformSteps() и NextStep() являются многоразовыми, только реализация в ButtonClickCoRoutine() будет изменяться с каждым различным сайтом реализации.

Я не совсем уверен, что мне нравится эта модель, и я не удивлюсь, если бы был более простой способ ее реализации. Но я не смог найти его в "interwebs" или MSDN или где-либо еще. Заранее спасибо за помощь.

4b9b3361

Ответ 1

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

С другой стороны, вы также должны посмотреть Parallel Extensions и его подход к продолжениям. Параллельные расширения являются частью .NET 4.0, тогда как CCR требует отдельного лицензирования. Я бы посоветовал вам пойти с каркасом, написанным людьми, которые едят, дышат и спят этот материал. Это просто слишком легко, чтобы получить неверные данные.

Ответ 2

Reactive Extensions for.NET предоставляют более чистую модель для обработки этого.

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

Ответ 3

Я не читал все это.

Они используют эту стратегию в студии робототехники CCR, и ряд других проектов использует эту стратегию. Альтернативой является использование LINQ, см., Например, этот блог для описания. Реактивная структура (Rx) построена по этим строкам.

Luca упоминает в своем PDC talk, что, возможно, будущая версия С#/VB может добавлять асинхронные примитивы к языку.

Тем временем, если вы можете использовать F #, это выигрышная стратегия. Прямо сейчас, что вы можете сделать с F #, здесь удаляет все остальное прямо из воды.

ИЗМЕНИТЬ

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

// a sample client function that runs synchronously 
let SumSquares (client : IMyClientContract) = 
    (box client :?> IClientChannel).Open() 
    let sq1 = client.Square(3) 
    let sq2 = client.Square(4) 
    (box client :?> IClientChannel).Close() 
    sq1 + sq2 

и соответствующий асинхронный код будет

// async version of our sample client - does not hold threads 
// while calling out to network 
let SumSquaresAsync (client : IMyClientContract) = 
    async { do! (box client :?> IClientChannel).OpenAsync() 
            let! sq1 = client.SquareAsync(3) 
            let! sq2 = client.SquareAsync(4) 
            do! (box client :?> IClientChannel).CloseAsync() 
            return sq1 + sq2 } 

Без сумасшедших обратных вызовов вы можете использовать конструкторы управления, такие как if-then-else, while, try-finally и т.д., пишите его почти так же, как вы пишете прямолинейный код, и все работает, но теперь оно асинхронно. Очень легко взять определенную пару методов BeginFoo/EndFoo и сделать соответствующие методы асинхронного F # для использования в этой модели.

Ответ 4

Вы также можете рассмотреть Jeffrey Richter AsyncEnumerator, который является частью его библиотеки "power threading". Он работал вместе с командой CCR для разработки CCR. AsyncEnumerator, по словам Джеффри, более "легкий", чем CCR. Лично я играл с AsyncEnumerator, но не с CCR.

Я не использовал его в гневе, хотя до сих пор я нашел ограничения с использованием счетчиков для выполнения сопрограммы слишком болезненными. В настоящее время изучается F # из-за других асинхронных рабочих процессов (если я правильно помню имя), которые выглядят как полноценные сопрограммы или "продолжения" (я забываю правильное имя или точные различия между терминами).

В любом случае, здесь некоторые ссылки:

http://www.wintellect.com/PowerThreading.aspx

Канал 9 видео на AsyncEnumerator

Статья MSDN