Я пишу рефакторинг программы 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 или где-либо еще. Заранее спасибо за помощь.