// Задача: вызывать Fun и получить "n".
//
// После этого последовательны должны быть вызваны:
//
// Action2 (кроме случаев, когда "n" не делится на 2)
// Action3 (кроме случаев, когда "n" не делится на 3)
// Action4 (кроме случаев, когда "n" не делится на 4)
Задача совершенно практическая. Fun может возвращать список необходимых обновлений, а Action2-Action4 производить эти обновления.
В синхронном варианте это решается так:
var service = new Service();
// Синхронный вариант
{
int n = service.Fun();
if (0 == n%2)
service.Action2();
if (0 == n%3)
service.Action3();
if (0 == n%4)
service.Action4();
Console.WriteLine("Complete");
}
Как видим -- ничего сложного. Можно добавлять сколько угодно Action -- сложность системы от этого не будет возрастать.
А теперь попробуем написать асинхронный вариант. Замечу, что потребность взята не с потолка -- при использовании Windows Phone нет возможности вызова веб-методов синхронно -- только асинхронный вариант. У меня получилось вот что:
var service = new Service();
// Асинхронный вариант 1
{
service.FunCompleted += (sender, eventArgs) =>
{
int n = eventArgs.Argument;
if (0 == n % 2)
{
service.Action2Completed +=
(o, myEventArgs) =>
{
if (0 == n % 3)
{
service.Action3Completed
+= (sender1, args1) =>
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
};
service.StartAction3();
}
else
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
}
};
service.StartAction2();
}
else
{
if (0 == n % 3)
{
service.Action3Completed
+= (o, myEventArgs) =>
{
if (0 == n%4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
};
service.StartAction3();
}
else
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
}
}
};
service.StartFun();
}
Обратите внимание: сложность нарастает по геометрической прогрессии! Для 3х Action имеем 7 условий. Для 4х их будет в 2 раза больше и так далее...
И, собственно, вопрос. Что делать, если таких вот Action в проекте действительно много? Как быть, как написать? Это ж идиотизм какой-то.
Для удобства привожу упрощенный код полностью:
Скрытый текст
using System;
using System.Threading;
namespace ConsoleApplication17
{
class Program
{
class MyEventArgs : EventArgs
{
public int Argument { get; set; }
}
private class Service
{
public event EventHandler<MyEventArgs> FunCompleted;
public event EventHandler Action2Completed;
public event EventHandler Action3Completed;
public event EventHandler Action4Completed;
public void StartFun()
{
(new Thread(() =>
{
Thread.Sleep(1000);
FunCompleted(this, new MyEventArgs { Argument = DateTime.Now.Second });
})).Start();
}
public void StartAction2()
{
(new Thread(() =>
{
Thread.Sleep(1000);
Action2Completed(this, null);
})).Start();
}
public void StartAction3()
{
(new Thread(() =>
{
Thread.Sleep(1000);
Action3Completed(this, null);
})).Start();
}
public void StartAction4()
{
(new Thread(() =>
{
Thread.Sleep(1000);
Action4Completed(this, null);
})).Start();
}
// Синхронный вариантpublic int Fun()
{
return DateTime.Now.Second;
}
public void Action2()
{
}
public void Action3()
{
}
public void Action4()
{
}
}
static void Main(string[] args)
{
// Задача: вызывать Fun 1 и получить "n".
//
// После этого последовательны должны быть вызваны:
//
// Action2 (кроме случаев, когда "n" не делится на 2)
// Action3 (кроме случаев, когда "n" не делится на 3)
// Action4 (кроме случаев, когда "n" не делится на 4)var service = new Service();
// Синхронный вариант
{
int n = service.Fun();
if (0 == n%2)
service.Action2();
if (0 == n%3)
service.Action3();
if (0 == n%4)
service.Action4();
Console.WriteLine("Complete");
}
// Асинхронный вариант 1
{
service.FunCompleted += (sender, eventArgs) =>
{
int n = eventArgs.Argument;
if (0 == n % 2)
{
service.Action2Completed +=
(o, myEventArgs) =>
{
if (0 == n % 3)
{
service.Action3Completed
+= (sender1, args1) =>
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
};
service.StartAction3();
}
else
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
}
};
service.StartAction2();
}
else
{
if (0 == n % 3)
{
service.Action3Completed
+= (o, myEventArgs) =>
{
if (0 == n%4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
};
service.StartAction3();
}
else
{
if (0 == n % 4)
{
service.Action4Completed +=
(o1, eventArgs1) =>
{
Console.WriteLine(
"Complete");
};
service.StartAction4();
}
}
}
};
service.StartFun();
}
Console.ReadLine();
}
}
}
Здравствуйте, Аноним, Вы писали:
А>И, собственно, вопрос. Что делать, если таких вот Action в проекте действительно много? Как быть, как написать? Это ж идиотизм какой-то.
Спросите здесь.
Не факт, что их ответ вам подойдет на 100%, но чем черт не шутит.
Re[2]: Лавинообразная сложность асинхронных методов
От:
Аноним
Дата:
09.04.12 21:26
Оценка:
Здравствуйте, Lloyd, Вы писали:
L>Спросите здесь. L>Не факт, что их ответ вам подойдет на 100%, но чем черт не шутит.
А если серьезно? Конечно, подразумевалось что гербалаNemerle не предлагать. Даже не знаю как написать, чтобы этого говна не было. Придумал 2 способа, один другого "краше".
1. Все варианты выбора вынести в специальный метод:
private void StartNext()
{
switch(_currentStep)
{
case 1:
if (0 == 2%_result)
_service.StartAction4();
else
...
break;
case ...
}
}
2. Обернуть методы и события сервиса в обертку. При этом вызов метода StartAction не всегда будет вызывать веб-метод, но в любом случае будет вызывать событие ActionComlete.
Но оба уродские.
Вы бы как сделали?
Re[3]: Лавинообразная сложность асинхронных методов
Здравствуйте, Аноним, Вы писали:
L>>Не факт, что их ответ вам подойдет на 100%, но чем черт не шутит.
А>А если серьезно? Конечно, подразумевалось что гербалаNemerle не предлагать.
Может тогда имеет смысл F# посмотреть. Вроде там был функционал для работы с асинхронностью.
Re[4]: Лавинообразная сложность асинхронных методов
От:
Аноним
Дата:
09.04.12 21:43
Оценка:
Здравствуйте, Lloyd, Вы писали:
L>Может тогда имеет смысл F# посмотреть. Вроде там был функционал для работы с асинхронностью.
К сожалению, можно использовать только C#.
Уже по всякому пробовал -- и Rx пробовал задействовать -- ничего не помогло. Везде получаются эти спагетти.
Мне вот интересно как бы вы сделали. А то всегда боюсь, что мой код попадет к другому человеку и он будет меня высмеивать, увидев эти макароны.
Как вы думаете, какой из двух предложенных мной способов лучше?
Здравствуйте, Аноним, Вы писали:
А> // Задача: вызывать Fun и получить "n". А> // А> // После этого последовательны должны быть вызваны: А> // А> // Action2 (кроме случаев, когда "n" не делится на 2) А> // Action3 (кроме случаев, когда "n" не делится на 3) А> // Action4 (кроме случаев, когда "n" не делится на 4)
А> // Асинхронный вариант 1
Если там всего лишь обновления экрана, может правильней запустить все три действия сразу, и пусть они выполнятся в неопределённой последовательности? Причина-то есть в цепочку выстраивать?
Если допустить, что причина есть... ну можно вот так забабахать:
List<EventHandler> seq = new List<EventHandler>();
EventHandler h = (o, eventArgs) => {
lock(seq) if (seq.Any()) {
var a = seq.First();
seq.RemoveAt(0);
a.Invoke(o, eventArgs);
}
};
service.Action2Completed += h;
service.Action3Completed += h;
service.Action4Completed += h;
service.FunCompleted += (o, eventArgs) => {
int n = (eventArgs as MyEventArgs).Argument;
lock (seq) {
seq.Clear();
if (0 == n % 2)
seq.Add((oo, ea) => service.StartAction2());
if (0 == n % 3)
seq.Add((oo, ea) => service.StartAction3());
if (0 == n % 4)
seq.Add((oo, ea) => service.StartAction4());
h.Invoke(o, eventArgs);
}
};
Здравствуйте, Аноним, Вы писали:
А>Мне вот интересно как бы вы сделали. А то всегда боюсь, что мой код попадет к другому человеку и он будет меня высмеивать, увидев эти макароны.
А>Как вы думаете, какой из двух предложенных мной способов лучше?
К сожалению, не могу ответить. У меня когда какая-нить дилемма возникает, я руками пробую разные варианты и смотрю, что в итоге выходит.
Тут не тот случай — мне не доводилось сталкиваться с большим количеством асинхронного кода и поэтому не владею предметом в достаточной степени, чтобы вам что-то советовать.
Вот ещё чудеса сишарпа. Можно почти не менять код синхронного варианта, и сделать его асинхронным
sealed class MyAsyncDemo : IDisposable
{
IEnumerable<object> MainRoutine(int n)
{
//...if (0 == n % 2)
{
service.Action2Completed += callback;
service.StartAction2();
yield return null;
// в этой точке оказываемся после выполнения Action2, имея её результаты как this.resultArgsif (0 == n % 4)
{
service.Action4Completed += callback;
service.StartAction4();
yield return null;
}
// в этой точке оказываемся после выполнения Action4 - т.е. можно сложную логику с кучей ветвлений
}
if (0 == n % 3)
{
service.Action3Completed += callback;
service.StartAction3();
yield return null;
}
}
public MyAsyncDemo(Service service, int n)
{
this.service = service;
callback = new EventHandler(Hdlr);
state = MainRoutine(n).GetEnumerator();
if (!state.MoveNext())
this.Dispose();
}
public void Dispose()
{
state.Dispose();
}
object sender;
EventArgs resultArgs;
EventHandler callback;
Service service;
IEnumerator<object> state;
void Hdlr(object sender, EventArgs args)
{
this.sender = sender;
this.resultArgs = args;
if (!state.MoveNext())
this.Dispose();
}
}
Вызов просто new MyAsyncDemo(service, n); в обработчике FunCompleted
Но вообще — асинхронность, как известно, не нужна
Забанен с формулировкой "клинический дисидент".
Re[2]: Лавинообразная сложность асинхронных методов
От:
Аноним
Дата:
09.04.12 22:19
Оценка:
Здравствуйте, b-3, Вы писали:
b-3>Если допустить, что причина есть... ну можно вот так забабахать:
Да, 3-й способ. К сожалению, не будет выглядеть так компактно, если веб-методы возвращают различные eventArgs и их нужно по-разному обработать. Но все-таки позволяет избежать лавинообразного нарастания сложности. Хотя и выглядит значительно сложнее синхронного варианта.
b-3>Или отрефакторить код в духе этого паттерна.
Там шаблон для синхронного варианта. Но, если я правильно понял, нечто подобное можно сделать и асинхронно -- это второй вариант, который я описал выше: http://rsdn.ru/forum/dotnet/4695359.aspx?tree=tree
b-3>Или развлечься и сделать кустарный async/await на основе yield return
Есть ли образец как сделать? Точно это облегчит жизнь? Ведь ключевых слов async/await в Windows Phone нет. Task то можно написать, но если обертывать вручную -- то очень сомнительно что будет проще...
b-3>А вообще, постановка задачи странная
Есть требование чтобы процессы выполнялись последовательно. Основная причина -- так красивее отображать информацию пользователю. Теоретически можно и одновременно -- но придется переделывать GUI. Да и неудобно как то говорить, что "не получается сделать последовательно, давайте будем делать одновременно". Вроде не мальчик уже, так что нужно делать как требуется, а не как легче.
Кроме того, все-равно нужно отследить момент, когда все задания выполнены и форму индикации обновления можно закрывать (если задания выполняются в разброс -- добавляется еще и список выполненных/не выполненных + блокировки).
Как видите -- проблема не надумана.
Re[2]: yield return вместо async/await
От:
Аноним
Дата:
09.04.12 22:51
Оценка:
Здравствуйте, b-3, Вы писали:
b-3>Вот ещё чудеса сишарпа. Можно почти не менять код синхронного варианта, и сделать его асинхронным
Идея понятна. Да, лихо. Идея похожа на список, но с синтаксическим сахаром.
Ну а практически вы бы какой способ использовали? Когда требуется максимальная простота.
Просто сейчас не знаю как и написать. Пример то мой упрощен. В реальности нужно предусмотреть и обработку ошибок, и восстановление сессии в случае обрыва, и обработку полученных данных. Все выглядит настолько макаронистым -- что просто руки опускаются. Тарелка с лапшой получается.
Проклял 10 раз Микрософта за то что лишили возможности использовать синхронные методы. Сколько раз говорили: никогда не считайте себя умнее пользователя. Они подумали, что мы будем забывать выносить вызов методов из UI потока и вообще лишили нас возможности вызывать методы в UI потоке. Умные. Но вот что делать в таком случае -- они не подумали...
b-3>Но вообще — асинхронность, как известно, не нужна
Верно, в данном случае она не просто не нужна, но очень сильно усложняет задачу. Но ее заставляют использовать принудительно. Видимо WP7 и Silverlight не рассчитаны для таких сложных UseCase. Там должен быть один максимум 2 Web-метода на все приложение.
Здравствуйте, <Аноним>, Вы писали:
А>Есть такая задача:
А>
А> // Задача: вызывать Fun и получить "n".
А> //
А> // После этого последовательны должны быть вызваны:
А> //
А> // Action2 (кроме случаев, когда "n" не делится на 2)
А> // Action3 (кроме случаев, когда "n" не делится на 3)
А> // Action4 (кроме случаев, когда "n" не делится на 4)
А>
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Lloyd, Вы писали:
L>>Может тогда имеет смысл F# посмотреть. Вроде там был функционал для работы с асинхронностью.
А>К сожалению, можно использовать только C#.
А>Уже по всякому пробовал -- и Rx пробовал задействовать -- ничего не помогло. Везде получаются эти спагетти.
А>Мне вот интересно как бы вы сделали. А то всегда боюсь, что мой код попадет к другому человеку и он будет меня высмеивать, увидев эти макароны.
А>Как вы думаете, какой из двух предложенных мной способов лучше?
А чем Rx не помог? Он же нормально такое будет описывать
from n in service.Fun()
from _2 in Observable.If(n%2 == 0, service.Action2(), Observable.Empty<Unit>())
from _3 in Observable.If(n%3 == 0, service.Action3(), Observable.Empty<Unit>())
from _4 in Observable.If(n%4 == 0, service.Action4(), Observable.Empty<Unit>())
select"Completed";