none
Событийное программирование RRS feed

  • Вопрос

  • Столкнулся с непониманием идеи на конкретном примере, но вопрос значительно более общий.

    Идеология Windows рекомендует и постоянно подталкивает к событийному программированию. Многие даже по своей природе синхронные события (в моем понимании - это подразумевающие строгий порядок выполнения) всеми мыслимыми и немыслимыми вариантами переводят в асинхронные режимы. И потом выдумывают новые велосипеды для синхронизации асинхронных операций. Хорошим примером является работа с сетью, где ожидание сервером вроде как надо делать асинхронным, но при обмене данными протокол требует четкой синхронизации.

    К примеру как разумно реализовать следующую идеологию:

    C# компонент WebBrowser, необходимо выдержать следующий протокол для автоматизации действий:

        Загрузить страницу
        Найти определенную форму заполнить поля и отправить
        Полученный результат сохранить.

    При выполнении этих пунктов необходимо постоянно ожидать полной загрузки страницы. Но окончание загрузки страницы хоть и выполняется в тоже потоке все же приходит асинхронно, и чтоб весь пример работы с одним сайтом был в одной процедуре приходится реализовывать такой бред:
    private void button1_Click(object sender, EventArgs e)
            {
                FinishLoading = false;
    
                webBrowser.DocumentCompleted += (s, ev) =>
                {
                    if ( ((System.Windows.Forms.WebBrowser)s).ReadyState == WebBrowserReadyState.Complete )
                    {
                        FinishLoading = true;
                    }
                };
    
                webBrowser.Navigate("http://google.com.ua");
    
                while (!FinishLoading)
                {
                    Application.DoEvents();
                };
                MessageBox.Show("Сайт загружен полностью");
    
            }
    


    Но особенно цикл проверок бесполезно нагружает процессор. Подскажите мне правильное направление. Ведь сайты разные, количество действий с ними тоже отличается, но все это приходит в один DocumentCompleted, а необходимо еще до Navigate засекать время.

    Суть вопроса каким способом выполнять асинхронные операции линейно чтоб выдержать протокол (строгая последовательность шагов и реакций на предыдущие результаты)?
    15 ноября 2011 г. 11:23

Ответы

  • Сделайте так:
                while (!FinishLoading)
                {
                    Thread.Sleep(1);
                    Application.DoEvents();
                };
    
    И загрузка процессора сразу упадет до нуля.
     
    Что касается асинхронных операций, то нужно рассматривать программу как автомат, который имеет конечное число состояний. А асинхронные операции переводят автомат из одного состояния в другое. При этом, все события можно обрабатывать одним и тем же обработчиком, и в нем же реализовывать "линейный" протокол. При этом, обработчику передается текущее состояние автомата, и он же это состояние может менять, и он же вызывает себя рекурсивно.
     
    Схематично так:
                void AsyncOperationHandler(State currentState)
                {
                    switch (currentState)
                    {
                        case State.Start:
                            AsyncConnect(AsyncOperationHandler, State.Connected);
                            break;
                        case State.Connected:
                            AsyncSendData(AsyncOperationHandler, State.Sent);
                            break;
                        case State.Sent:
                            AsyncReceiveData(AsyncOperationHandler, State.Received);
                            break;
                        case State.Received:
                            AsyncClose(AsyncOperationHandler, State.Closed);
                            break;
                        case State.Closed:
                            break;
                    }
                }
    

    Это, разумеется, только один из вариантов. Наверно можно делать и по-другому...

    Что касается WebBrowser то это вообще отдельный COM-объект, который живет своей жизнью, и не обязан предоставлять вам синхронный операции. Если хотите синхронные - используйте Socket, WebClinet и т.п. Эти классы имеют и синхронные и асинхронные методы.



    • Изменено Algol36Editor 15 ноября 2011 г. 12:19
    • Помечено в качестве ответа PhantomSL 15 ноября 2011 г. 13:44
    15 ноября 2011 г. 12:10
    Отвечающий
  • > C# компонент WebBrowser, необходимо выдержать следующий протокол для автоматизации действий: Загрузить страницу; Найти определенную форму заполнить поля и отправить; Полученный результат сохранить.

      

    using System;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication0
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                this.Size = new System.Drawing.Size(700, 500);
                var rtb = new RichTextBox { Parent = this, Dock = DockStyle.Fill };
                this.Menu = new MainMenu();
                this.Menu.MenuItems.Add("Start", (s, e) => this.Run("http://bing.com", rtb.AppendText));
            }
    
            void Run(string url, Action<string> fn)
            {
                var wb = new WebBrowser();
                wb.DocumentCompleted += (s, e) =>
                {
                    switch (e.Url.LocalPath)
                    {
                        case "/":
                            dynamic input = wb.Document.GetElementById("sb_form_q").DomElement;
                            dynamic go = wb.Document.GetElementById("sb_form_go").DomElement;
                            input.value = "malobukv msdn";
                            go.click();
                            break;
                        case "/search":
                            fn(wb.Document.Body.InnerText);
                            break;
                    }
                };
                wb.Navigate(url);
            }
        }
    }
    
    

     

    • Предложено в качестве ответа Malobukv 15 ноября 2011 г. 12:41
    • Помечено в качестве ответа PhantomSL 15 ноября 2011 г. 13:48
    15 ноября 2011 г. 12:41

Все ответы

  • Сделайте так:
                while (!FinishLoading)
                {
                    Thread.Sleep(1);
                    Application.DoEvents();
                };
    
    И загрузка процессора сразу упадет до нуля.
     
    Что касается асинхронных операций, то нужно рассматривать программу как автомат, который имеет конечное число состояний. А асинхронные операции переводят автомат из одного состояния в другое. При этом, все события можно обрабатывать одним и тем же обработчиком, и в нем же реализовывать "линейный" протокол. При этом, обработчику передается текущее состояние автомата, и он же это состояние может менять, и он же вызывает себя рекурсивно.
     
    Схематично так:
                void AsyncOperationHandler(State currentState)
                {
                    switch (currentState)
                    {
                        case State.Start:
                            AsyncConnect(AsyncOperationHandler, State.Connected);
                            break;
                        case State.Connected:
                            AsyncSendData(AsyncOperationHandler, State.Sent);
                            break;
                        case State.Sent:
                            AsyncReceiveData(AsyncOperationHandler, State.Received);
                            break;
                        case State.Received:
                            AsyncClose(AsyncOperationHandler, State.Closed);
                            break;
                        case State.Closed:
                            break;
                    }
                }
    

    Это, разумеется, только один из вариантов. Наверно можно делать и по-другому...

    Что касается WebBrowser то это вообще отдельный COM-объект, который живет своей жизнью, и не обязан предоставлять вам синхронный операции. Если хотите синхронные - используйте Socket, WebClinet и т.п. Эти классы имеют и синхронные и асинхронные методы.



    • Изменено Algol36Editor 15 ноября 2011 г. 12:19
    • Помечено в качестве ответа PhantomSL 15 ноября 2011 г. 13:44
    15 ноября 2011 г. 12:10
    Отвечающий
  • Боюсь я чего-то конечных автоматов как огня, хотя не разу их не организовывал.

     

    На счет загрузки процессора я честно говоря не проверял, так что за совет спасибо.

    А вы лично как думаете когда оправдано реализовывать подобные костыли ( while (!FinishLoading) ), а когда заморачиваться с конечным автоматом?


    15 ноября 2011 г. 12:33
  • > C# компонент WebBrowser, необходимо выдержать следующий протокол для автоматизации действий: Загрузить страницу; Найти определенную форму заполнить поля и отправить; Полученный результат сохранить.

      

    using System;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication0
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                this.Size = new System.Drawing.Size(700, 500);
                var rtb = new RichTextBox { Parent = this, Dock = DockStyle.Fill };
                this.Menu = new MainMenu();
                this.Menu.MenuItems.Add("Start", (s, e) => this.Run("http://bing.com", rtb.AppendText));
            }
    
            void Run(string url, Action<string> fn)
            {
                var wb = new WebBrowser();
                wb.DocumentCompleted += (s, e) =>
                {
                    switch (e.Url.LocalPath)
                    {
                        case "/":
                            dynamic input = wb.Document.GetElementById("sb_form_q").DomElement;
                            dynamic go = wb.Document.GetElementById("sb_form_go").DomElement;
                            input.value = "malobukv msdn";
                            go.click();
                            break;
                        case "/search":
                            fn(wb.Document.Body.InnerText);
                            break;
                    }
                };
                wb.Navigate(url);
            }
        }
    }
    
    

     

    • Предложено в качестве ответа Malobukv 15 ноября 2011 г. 12:41
    • Помечено в качестве ответа PhantomSL 15 ноября 2011 г. 13:48
    15 ноября 2011 г. 12:41
  • > сайты разные, количество действий с ними тоже отличается, но все это приходит в один DocumentCompleted, а необходимо еще до Navigate засекать время.

     

    можено переделать предыдущий пример. 
    добавить класс Info ...
      

    using System;
    using System.Windows.Forms;
    using System.Diagnostics;
    
    namespace WindowsFormsApplication0
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                this.Size = new System.Drawing.Size(700, 500);
                var rtb = new RichTextBox { Parent = this, Dock = DockStyle.Fill };
                this.Menu = new MainMenu();
                this.Menu.MenuItems.Add("Start", (s, e) =>
                    this.Run("http://bing.com", info => rtb.AppendText(info.ElapsedMilliseconds + "\n" + info.Result))
                    );
            }
    
            class Info
            {
                public string Url { get; set; }
                public long ElapsedMilliseconds { get; set; } 
                public string Result { get; set; }
            }
    
            void Run(string url, Action<Info> fn)
            {
                var wb = new WebBrowser();
                var info = new Info { Url = url };
                var sw = new Stopwatch();
                wb.DocumentCompleted += (s, e) =>
                {
                    switch (e.Url.LocalPath)
                    {
                        case "/":
                            dynamic input = wb.Document.GetElementById("sb_form_q").DomElement;
                            dynamic go = wb.Document.GetElementById("sb_form_go").DomElement;
                            input.value = "malobukv msdn";
                            go.click();
                            break;
                        case "/search":
                            sw.Stop();
                            info.Result = wb.Document.Body.InnerText;
                            info.ElapsedMilliseconds = sw.ElapsedMilliseconds;
                            fn(info);
                            break;
                    }
                };
                sw.Start();
                wb.Navigate(url);
            }
        }
    }
    
    

    15 ноября 2011 г. 12:56
  • На счет загрузки процессора я честно говоря не проверял, так что за совет спасибо.

    А вы лично как думаете когда оправдано реализовывать подобные костыли ( while (!FinishLoading) ), а когда заморачиваться с конечным автоматом?


    Костыль тут скорее даже не FinishLoading, а Application.Doevents - вот этого нужно всячески избегать.

    Что касается вашего простейшего случая, то конечно здесь не нужно заморачиваться с автоматом. Достаточно просто обработчика события, типа такого, как вам привели. Но вот при асинхронной реализации скажем smtp протокола, без состояний точно не обойтись.

    15 ноября 2011 г. 15:04
    Отвечающий