Ловушки (Mis) Используя итераторы C# для реализации сопрограмм

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

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

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

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

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

Исходная non-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 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 result, out T store ) { 
    store = result;
    PerformSteps();  // continues iterating steps
}

private IEnumerable m_StepsToPerform;
private void PerformSteps( IEnumerable 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 управлять, когда каждый шаг выполняется - в основном, когда последний асинхронный вызов WCF завершается. В то время как на поверхности это похоже на большее количество кода, стоит упомянуть это PerformSteps() и NextStep() являются допускающими повторное использование, только реализация в ButtonClickCoRoutine() изменился бы с каждым различным сайтом реализации.

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

15
задан Community 23 May 2017 в 12:02
поделиться

4 ответа

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

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

11
ответ дан 1 December 2019 в 04:17
поделиться

Реактивные расширения для .NET предоставляют гораздо более четкую модель для решения этой проблемы.

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

4
ответ дан 1 December 2019 в 04:17
поделиться

Вы также можете рассмотреть AsyncEnumerator Джеффри Рихтера, который является частью его библиотеки «power threading». Он работал вместе с командой CCR над разработкой CCR. По словам Джеффри, AsyncEnumerator более «легкий», чем CCR. Лично я играл с AsyncEnumerator, но не с CCR.

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

В любом случае. , вот несколько ссылок:

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

Видео канала 9 на AsyncEnumerator

Статья MSDN

0
ответ дан 1 December 2019 в 04:17
поделиться

Я не читал вашу книгу целиком.

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

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

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

EDIT

Чтобы процитировать пример из моего блога, предположим, что у вас есть клиент 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 # для использования в этой модели.

1
ответ дан 1 December 2019 в 04:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: