Я пишу рефакторинг программы 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 или где-либо еще. Заранее спасибо за помощь.
Нет хорошего ответа, кроме того, что мы чувствуем вашу боль. Мы проводим много исследований о том, как люди используют асинхронность в Silverlight и других средах. Виды преобразований, которые мы делаем, чтобы сделать итераторы похожими на дешевые сопрограммы, являются началом, но не имеют достаточной общности для решения всех проблем в этом пространстве. Я определенно хотел бы, чтобы такая модель была четко выражена на языке, так же, как мы чисто выражаем стороны генерации (доходности) и потребления (foreach) шаблона итератора. –
Я думаю, этот вопрос объясняет мотивацию добавления async/wait к C#. –