Я пишу, что рефакторинг программы 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, или где-либо еще. Заранее спасибо за справку.
Вам обязательно стоит взглянуть на Среда выполнения параллелизма и координации . Он использует итераторы именно для этой цели.
С другой стороны, вам также следует взглянуть на Parallel Extensions и его подход к продолжениям. Параллельные расширения являются частью .NET 4.0, тогда как CCR требует отдельного лицензирования. Я советую вам использовать фреймворк, написанный людьми, которые едят, дышат и спят все это. Слишком легко самостоятельно ошибиться в деталях.
Реактивные расширения для .NET предоставляют гораздо более четкую модель для решения этой проблемы.
Они предоставляют расширения, которые позволяют писать простые делегаты для защиты от асинхронных событий в значительной степени, намного чище. Я рекомендую изучить их и адаптировать к этой ситуации.
Вы также можете рассмотреть AsyncEnumerator Джеффри Рихтера, который является частью его библиотеки «power threading». Он работал вместе с командой CCR над разработкой CCR. По словам Джеффри, AsyncEnumerator более «легкий», чем CCR. Лично я играл с AsyncEnumerator, но не с CCR.
Я не использовал его в гневе - до сих пор я обнаружил, что ограничения с использованием счетчиков для реализации сопрограмм слишком болезненны. В настоящее время изучаю F # из-за, помимо прочего, асинхронных рабочих процессов (если я правильно помню имя), которые выглядят как полноценные сопрограммы или «продолжения» (я забыл правильное имя или точные различия между терминами).
В любом случае. , вот несколько ссылок:
http://www.wintellect.com/PowerThreading. aspx
Я не читал вашу книгу целиком.
Они используют эту стратегию в студии робототехники 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 # для использования в этой модели.