Я пишу рефакторинг программы Silverlight для использования части ее существующей бизнес-логики из службы WCF. При этом я столкнулся с ограничением в Silverlight 3, которое позволяет только асинхронные вызовы к службам WCF, чтобы избежать случаев, когда длительные или неотвечающие вызовы служб блокируют поток пользовательского интерфейса (SL имеет интересную модель очередей для вызова служб WCF). в потоке пользовательского интерфейса).
Как следствие, написание того, что когда-то было простым, становится все более сложным ( см. Примеры кода в конце моего вопроса ).
В идеале я бы использовал сопрограммы , чтобы упростить реализацию, но, к сожалению, C # в настоящее время не поддерживает сопрограммы в качестве средства родного языка. Однако в C # есть концепция генераторов (итераторов), использующих синтаксис yield return
. Моя идея состоит в том, чтобы переопределить ключевое слово yield, чтобы позволить мне построить простую модель сопрограммы для той же логики.
Однако я неохотно делаю это, потому что я обеспокоен тем, что могут быть некоторые скрытые (технические) ловушки, которых я не ожидаю (учитывая мою относительную неопытность с Silverlight и WCF). Я также обеспокоен тем, что механизм реализации может быть неясен будущим разработчикам и может препятствовать, а не упрощать их усилия по поддержке или расширению кода в будущем. Я видел этот вопрос на SO о повторном назначении итераторов для построения конечных автоматов: реализация конечного автомата с использованием ключевого слова yield "1013 *, и хотя это не совсем то же самое, что я делаю, оно делает заставь меня задуматься.
Однако мне нужно что-то сделать, чтобы скрыть сложность вызовов службы и управлять усилиями и потенциальным риском возникновения дефектов в этом типе изменений. Я открыт для других идей или подходов, которые могу использовать для решения этой проблемы.
Исходная версия кода, отличная от 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 двигаться, когда выполняется каждый шаг - в основном, когда завершается последний асинхронный вызов WCF. Хотя на первый взгляд это выглядит как дополнительный код, стоит отметить, что PerformSteps()
и NextStep()
можно использовать повторно, только реализация в ButtonClickCoRoutine()
будет меняться с каждым отдельным сайтом реализации.
Я не совсем уверен, что мне нравится эта модель, и я бы не удивился, если бы существовал более простой способ ее реализации. Но я не смог найти ни одного на «межсетях» или MSDN, или где-либо еще. Заранее спасибо за помощь.