2009-12-14 2 views
15

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

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

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

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

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

Оригинальная версия не-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 или где-либо еще. Заранее спасибо за помощь.

+7

Нет хорошего ответа, кроме того, что мы чувствуем вашу боль. Мы проводим много исследований о том, как люди используют асинхронность в Silverlight и других средах. Виды преобразований, которые мы делаем, чтобы сделать итераторы похожими на дешевые сопрограммы, являются началом, но не имеют достаточной общности для решения всех проблем в этом пространстве. Я определенно хотел бы, чтобы такая модель была четко выражена на языке, так же, как мы чисто выражаем стороны генерации (доходности) и потребления (foreach) шаблона итератора. –

+0

Я думаю, этот вопрос объясняет мотивацию добавления async/wait к C#. –

ответ

11

Обязательно посмотрите на Concurrency and Coordination Runtime. Он использует итераторы именно для этой цели.

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

+0

Согласен. Я понимаю, что у CCR были определенные лицензионные ограничения (которые, если я их правильно пойму, сделают невозможным использование). Библиотека параллельных расширений и .NET 4.0 в настоящее время находится в предварительном просмотре - я посмотрел на них, но я не вижу никаких классов или возможностей для такого рода вещей. Была ли определенная часть PTE, которую вы имели в виду? – LBushkin

+4

LBushkin: Вы можете получить параллельные расширения, входящие в состав Reactive Extensions (см. Мой пост для ссылки). Существует метод Task.FromAsync (+ overloads), который позволяет вам создать задачу из пары методов на основе IAsyncResult, а также использовать Task.ContinueWith для продолжения. –

+0

Рид: Спасибо. Я посмотрю на это. – LBushkin

4

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

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

+0

Я посмотрел на Rx.NET, однако он находится в предварительном просмотре и, вероятно, не будет доступен для моих таймфреймов. Я также не совсем понимаю, как писать этот тип кода RX.NET. Я видел примеры, иллюстрирующие обработку источников событий как IObservable <> - по существу бесконечные итераторы, но не как структурировать этот шаблон. Знаете ли вы о каких-либо примерах, на которые я мог бы обратить внимание? – LBushkin

+0

Взгляните на это: http://themechanicalbride.blogspot.com/2009/07/developing-with-rx-part-2-converting.html Требуется небольшая библиотека обертки, но было бы легко расширить ее с помощью продолжений. Тем не менее, есть довольно много образцов, из которых можно сделать задачи из наблюдаемых с Rx. –

+0

+1 RX - это направление для такого рода вещей в Silverlight. Я не должен слишком беспокоиться о статусе «Предварительный просмотр» Rx, если это действительно проблема, возможно, Silverlight целиком не для вас. – AnthonyWJones

1

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

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

Luca упоминает в своем PDC talk, что, возможно, будущая версия 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 } 

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

+0

Как я ответил Риду и Джону, я посмотрел CCR, Parallel Extensions и Reactive.NET - однако я не видел хорошего примера, который я могу рассказать о том, что я делаю. Это помогло бы мне решить, какие (если они есть) из этих библиотек могут соответствовать законопроекту. – LBushkin

+0

У вас есть пример F #, на который я мог бы смотреть? Я не думаю, что могу использовать F # в приложении Silverlight 3, но я могу научиться чему-то из подхода, который он использует. – LBushkin

+0

Ознакомьтесь с обсуждением PDC Luca с 2008 года здесь: http://channel9.msdn.com/pdc2008/TL11/ В видеоролике, смотрите около 10 минут, начиная с 50 минут, он показывает, как легко совершать асинхронные вызовы в F #. Вы можете использовать библиотеку F # в приложении Silverlight 3. – Brian

0

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

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

Во всяком случае, вот некоторые ссылки:

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

Channel 9 video on AsyncEnumerator

MSDN Article

Смежные вопросы