Лавинообразная сложность асинхронных методов
От: Аноним  
Дата: 09.04.12 20:24
Оценка: 1 (1) +1
Есть такая задача:

            // Задача: вызывать 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();
        }
    }
}
Re: Лавинообразная сложность асинхронных методов
От: nikov США http://www.linkedin.com/in/nikov
Дата: 09.04.12 20:26
Оценка:
Здравствуйте, Аноним, Вы писали:

Asynchronous Programming with Async and Await
Re[2]: Лавинообразная сложность асинхронных методов
От: Аноним  
Дата: 09.04.12 20:35
Оценка:
Здравствуйте, nikov, Вы писали:

N>Asynchronous Programming with Async and Await


К сожалению, не подходит для Windows Phone...
Re: Лавинообразная сложность асинхронных методов
От: Lloyd Россия  
Дата: 09.04.12 21:13
Оценка: :))
Здравствуйте, Аноним, Вы писали:

А>И, собственно, вопрос. Что делать, если таких вот 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]: Лавинообразная сложность асинхронных методов
От: Lloyd Россия  
Дата: 09.04.12 21:38
Оценка:
Здравствуйте, Аноним, Вы писали:

L>>Не факт, что их ответ вам подойдет на 100%, но чем черт не шутит.


А>А если серьезно? Конечно, подразумевалось что гербалаNemerle не предлагать.


Может тогда имеет смысл F# посмотреть. Вроде там был функционал для работы с асинхронностью.
Re[4]: Лавинообразная сложность асинхронных методов
От: Аноним  
Дата: 09.04.12 21:43
Оценка:
Здравствуйте, Lloyd, Вы писали:

L>Может тогда имеет смысл F# посмотреть. Вроде там был функционал для работы с асинхронностью.


К сожалению, можно использовать только C#.

Уже по всякому пробовал -- и Rx пробовал задействовать -- ничего не помогло. Везде получаются эти спагетти.

Мне вот интересно как бы вы сделали. А то всегда боюсь, что мой код попадет к другому человеку и он будет меня высмеивать, увидев эти макароны.

Как вы думаете, какой из двух предложенных мной способов лучше?
Re: Лавинообразная сложность асинхронных методов
От: b-3 Россия  
Дата: 09.04.12 21:43
Оценка:
Здравствуйте, Аноним, Вы писали:

А> // Задача: вызывать 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);
    }    
};


Или отрефакторить код в духе этого паттерна.

Или развлечься и сделать кустарный async/await на основе yield return

А вообще, постановка задачи странная
Забанен с формулировкой "клинический дисидент".
Re[5]: Лавинообразная сложность асинхронных методов
От: Lloyd Россия  
Дата: 09.04.12 21:53
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Мне вот интересно как бы вы сделали. А то всегда боюсь, что мой код попадет к другому человеку и он будет меня высмеивать, увидев эти макароны.


А>Как вы думаете, какой из двух предложенных мной способов лучше?


К сожалению, не могу ответить. У меня когда какая-нить дилемма возникает, я руками пробую разные варианты и смотрю, что в итоге выходит.
Тут не тот случай — мне не доводилось сталкиваться с большим количеством асинхронного кода и поэтому не владею предметом в достаточной степени, чтобы вам что-то советовать.
Re: yield return вместо async/await
От: b-3 Россия  
Дата: 09.04.12 22:12
Оценка:
Здравствуйте, Аноним, Вы писали:

Вот ещё чудеса сишарпа. Можно почти не менять код синхронного варианта, и сделать его асинхронным

    sealed class MyAsyncDemo : IDisposable
    {
        IEnumerable<object> MainRoutine(int n)
        {
            //...
            if (0 == n % 2)
            {
                service.Action2Completed += callback;
                service.StartAction2();
                yield return null;
                // в этой точке оказываемся после выполнения Action2, имея её результаты как this.resultArgs

                if (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
Автор:
Дата: 10.04.12


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-метода на все приложение.
Re: Лавинообразная сложность асинхронных методов
От: Holms США  
Дата: 09.04.12 23:50
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Есть такая задача:


А>
А>            // Задача: вызывать Fun и получить "n".
А>            //
А>            // После этого последовательны должны быть вызваны:
А>            //
А>            // Action2 (кроме случаев, когда "n" не делится на 2)
А>            // Action3 (кроме случаев, когда "n" не делится на 3)
А>            // Action4 (кроме случаев, когда "n" не делится на 4)
А>


это смотрел?
... << RSDN@Home 1.2.0 alpha 5 (M4) rev. 1510>>
The life is relative and reversible.
Re: Лавинообразная сложность асинхронных методов
От: vdimas Россия  
Дата: 09.04.12 23:52
Оценка:
Здравствуйте, Аноним, Вы писали:

вроде идут повторы, начиная с конца.
А если вынести повторы в отдельные ф=ии?
static MyDelegate MakeAction4(Sevice sevice, int n) {
  return (o, eventArgs1) => {
    if (n % 4 == 0) {
      service.Action4Completed += (o1, eventArgs1) => { Console.WriteLine("Complete");                                        
      service.StartAction4();
    }
  }
}

static MyDelegate MakeAction3(Sevice sevice, int n) {
  return (o, eventArgs1) => {
    if (n % 3 == 0) {
      service.Action3Completed += MakeAction4(service, n);
      service.StartAction3();
    }
  }
}

static MyDelegate MakeAction2(Sevice sevice, int n) {
  return (o, eventArgs1) => {
    if (n % 2 == 0) {
      service.Action3Completed += MakeAction3(service, n);
      service.StartAction2();
    }
  }
}

...

var service = new Service();

service.FunCompleted += (sender, eventArgs) => { 
  int n = eventArgs.Argument;

  if (0 == n%2)
    MakeAction2(service, n)(sender, eventArgs); 
  else if (0 == n%3)
    MakeAction3(service, n)(sender, eventArgs); 
  else if (0 == n%4)
    MakeAction4(service, n)(sender, eventArgs);
}

service.StartFun();
Re[2]: Лавинообразная сложность асинхронных методов
От: Аноним  
Дата: 10.04.12 00:07
Оценка:
Здравствуйте, Holms, Вы писали:

H>это смотрел?


Ссылка на библиотеку битая: http://wintellect.com/PowerThreading.aspx
Re[3]: Лавинообразная сложность асинхронных методов
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.04.12 00:43
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, nikov, Вы писали:


N>>Asynchronous Programming with Async and Await


А>К сожалению, не подходит для Windows Phone...


Тогда reactive extensions
Re[3]: Лавинообразная сложность асинхронных методов
От: nikov США http://www.linkedin.com/in/nikov
Дата: 10.04.12 00:47
Оценка: :)
Здравствуйте, Аноним, Вы писали:

А>Ссылка на библиотеку битая: http://wintellect.com/PowerThreading.aspx


Битых ссылок не бывает

http://web.archive.org/web/20110718070146/http://www.wintellect.com/PowerThreading.aspx
http://wintellect.com/Downloads/PowerThreadingAttachments/Wintellect_Power_Threading_Library_(October%2026,%202010).zip
Re[5]: Лавинообразная сложность асинхронных методов
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.04.12 00:51
Оценка: 51 (2) +1
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, 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";


А можно еще круче:

service.Fun().SelectMany(n => 
    Observable.ForkJoin(
        Observable.If(n%2 == 0, service.Action2(), Observable.Empty<Unit>())
        Observable.If(n%3 == 0, service.Action3(), Observable.Empty<Unit>())
        Observable.If(n%4 == 0, service.Action4(), Observable.Empty<Unit>())
    )). Select(_ => "Completed");

Так еще и параллельно запускаться будут.
Re[2]: yield return вместо async/await
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.04.12 00:52
Оценка: +1 :)
Здравствуйте, b-3, Вы писали:


b-3>Но вообще — асинхронность, как известно, не нужна


Попробуй рассказать это silverlight\windows phone 7\windows 8\js
Re[6]: Лавинообразная сложность асинхронных методов
От: vdimas Россия  
Дата: 10.04.12 02:16
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>А чем Rx не помог? Он же нормально такое будет описывать


G>
G>from n in service.Fun()
G>from _2 in Observable.If(n%2 == 0, service.Action2(), Observable.Empty<Unit>())
G>from _3 in Observable.If(n%3 == 0, service.Action3(), Observable.Empty<Unit>())
G>from _4 in Observable.If(n%4 == 0, service.Action4(), Observable.Empty<Unit>())
G>select "Completed";
G>


А если целиком воспроизвести с подпиской на события с сохранением семантики?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.