2012-07-03 4 views
4

Я пытаюсь перемещаться по веб-сайту и выполнять некоторую работу на страницах программным образом с помощью элемента управления WebBrowser в форме Windows. Я нашел this, ища способ заблокировать мой поток до тех пор, пока не произойдет событие DocumentCompleted WebBrowser. Учитывая, что, вот мой текущий код:Приостановить поток до тех пор, пока WebBrowser не закончит загрузку

public partial class Form1 : Form 
{ 
    private AutoResetEvent autoResetEvent; 

    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     Thread workerThread = new Thread(new ThreadStart(this.DoWork)); 
     workerThread.SetApartmentState(ApartmentState.STA); 
     workerThread.Start(); 
    } 

    private void DoWork() 
    { 
     WebBrowser browser = new WebBrowser(); 
     browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted); 
     browser.Navigate(login_page); 
     autoResetEvent.WaitOne(); 
     // log in 

     browser.Navigate(page_to_process); 
     autoResetEvent.WaitOne(); 
     // process the page 
    } 

    private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
    { 
     autoResetEvent.Set(); 
    } 
} 

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

Теперь, исходя из того, что я понимаю, причина в том, что это не работает, потому что событие DocumentCompleted использует тот же поток, что и WaitOne(), поэтому событие не будет запущено до тех пор, пока WaitOne() не вернется (никогда , в этом случае).

Что интересно, если я добавлю элемент управления WebBrowser в форму из панели инструментов (перетаскивание и перетаскивание), а затем перейдите с помощью этого кода, этот код будет работать отлично (без каких-либо изменений, кроме как поместить вызов для навигации внутри вызов Invoke - см. ниже). Но если я вручную добавлю элемент управления WebBrowser в файл Designer, это не сработает. И я действительно не хочу видеть видимый WebBrowser в своей форме, я просто хочу сообщить о результатах.

public delegate void NavigateDelegate(string address); 
browser.Invoke(new NavigateDelegate(this.browser.Navigate), new string[] { login_page }); 

Мой вопрос, то это: Какой самый лучший способ приостановить поток до пожаров событий DocumentCompleted браузера?

ответ

0

HAH! У меня такой же вопрос. Вы можете сделать это с обработкой событий. Если вы остановите поток в середине страницы, ему нужно будет подождать, пока страница не закончится. Вы можете легко сделать это путем присоединения

Page.LoadComplete += new EventHandler(triggerFunction); 

В triggerFunction вы можете сделать это

triggerFunction(object sender, EventArgs e) 
{ 
    autoResetEvent.reset(); 
} 

Позвольте мне знать, если это работает. Я закончил тем, что не использовал потоки в моих и вместо этого просто помещал материал в triggerFunction. Некоторые синтаксис не может быть 100% правильно, потому что я отвечаю с верхней части моей головы

+0

Это в основном то, что у меня есть, за исключением того, что я делаю прямой вызов autoResetEvent.Set() (autoResetEvent.Reset() будет продолжать блокирование, не разблокирует) вместо использования промежуточного triggerFunction. Однако этот код не работает (опять же, из того, что я понимаю, это потому, что EventHandler выполняется в том же потоке, что и начальный вызов, поэтому, если поток блокируется бесконечно, событие никогда не будет срабатывать). – Chris

+0

Что-то кажется неправильным в вашей главной теме страницы, которая мешает ему завершить выполнение. Если вы приостановите свой поток, страница должна просто сохранить рендеринг (куры - это поток). Может быть, я просто показал неправильные вызовы? Тем не менее, если что-то вы хотите опубликовать в своем триггере событий, потому что он гарантирует, что страница закончила рендеринг (по крайней мере, как полагает сервер) –

+0

Я отредактировал свое сообщение, чтобы сделать обработку событий DocumentCompleted более понятной. Ясно, что что-то не так с тем, как работает поток, но я не могу понять, что это такое. – Chris

0

EDIT

регистра в методе Initialize компонент, как это, вместо того, чтобы в тот же метод.

WebBrowser browser = new WebBrowser(); 
WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted); 

ReadyState сообщит вам о ходе загрузки документа, когда он отмечен в событии DocumentCompleted.

void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
{ 
    if (browser.ReadyState == WebBrowserReadyState.Complete) 
{ 

} 
} 
+0

Если 'browser.ReadyState! = WebBrowserReadyState.Complete', у вас есть бесконечный цикл. «ReadyState» не изменится. Единственный способ, которым это может работать, - это если 'browser.ReadyState' уже будет' WebBrowserReadyState.Complete' (что имеет смысл: это событие 'DocumentCompleted'), но в этом случае вам не нужен цикл. – hvd

+0

Отредактировал свой ответ. Спасибо за указание. –

+0

Как я уже упоминал, событие DocumentCompleted никогда не срабатывает, поскольку оно выполняется в том же потоке, что и вызов Navigate(), и поэтому будет выполняться только после возвращения WaitOne() (чего никогда не происходит, поскольку вызов метода Set() никогда не производится). – Chris

1

Крис,

Я прохожу вас здесь возможную реализацию, которая решает эту проблему, но, пожалуйста, взглянуть на комментарии здесь, под которые мне пришлось столкнуться и исправить, прежде чем все работало, как я ожидал. Вот пример способа делать некоторые действия на странице в WebBrowser (обратите внимание, что WebBrowser является частью формы в моем случае):

internal ActionResponse CheckMessages() //Action Response is a custom class of mine to store some data coming from pages 
     { 
     //go to messages 
     HtmlDocument doc = WbLink.Document; //wbLink is a referring link to a webBrowser istance 
     HtmlElement ele = doc.GetElementById("message_alert_box"); 
     if (ele == null) 
      return new ActionResponse(false); 

     object obj = ele.DomElement; 
     System.Reflection.MethodInfo mi = obj.GetType().GetMethod("click"); 
     mi.Invoke(obj, new object[0]); 

     semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); //This is a simil-waitOne statement (1) 
     if (!semaphoreForDocCompletedEvent) 
      throw new Exception("sequencing of Document Completed events is failed."); 

     //get the list 
     doc = WbLink.Document; 
     ele = doc.GetElementById("mailz"); 
     if (!ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000)) //This is a simil-waitOne statement (2) 

      ele = doc.GetElementById("mailz"); 
     ele = doc.GetElementById("mailz"); 

     //this contains a tbody 
     HtmlElement tbody = ele.FirstChild; 

     //count how many elemetns are espionage reports, these elements are inline then counting double with their wrappers on top of them. 
     int spioCases = 0; 
     foreach (HtmlElement trs in tbody.Children) 
     { 
      if (trs.GetAttribute("id").ToLower().Contains("spio")) 
       spioCases++; 
     } 

     int nMessages = tbody.Children.Count - 2 - spioCases; 

     //create an array of messages to store data 
     GameMessage[] archive = new GameMessage[nMessages]; 

     for (int counterOfOpenMessages = 0; counterOfOpenMessages < nMessages; counterOfOpenMessages++) 
     { 

      //open first element 
      WbLink.ScriptErrorsSuppressed = true; 
      ele = doc.GetElementById("mailz"); 
      //this contains a tbody 
      tbody = ele.FirstChild; 

      HtmlElement mess1 = tbody.Children[1]; 
      int idMess1 = int.Parse(mess1.GetAttribute("id").Substring(0, mess1.GetAttribute("id").Length - 2)); 
      //check if subsequent element is not a spio report, in case it is then the element has not to be opened. 
      HtmlElement mess1Sibling = mess1.NextSibling; 
      if (mess1Sibling.GetAttribute("id").ToLower().Contains("spio")) 
      { 
       //this is a wrapper for spio report 
       ReadSpioEntry(archive, counterOfOpenMessages, mess1, mess1Sibling); 
       //delete first in line 
       DeleteFirstMessageItem(doc, ref ele, ref obj, ref mi, ref tbody); 
       semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); //This is a simil-waitOne statement (3) 

      } 
      else 
      { 
       //It' s anormal message 
       OpenMessageEntry(ref obj, ref mi, tbody, idMess1); //This opens a modal dialog over the page, and it is not generating a DocumentCompleted Event in the webBrowser 

       //actually opening a message generates 2 documetn completed events without any navigating event issued 
       //Application.DoEvents(); 
       semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); 

       //read element 
       ReadMessageEntry(archive, counterOfOpenMessages); 

       //close current message 
       CloseMessageEntry(ref ele, ref obj, ref mi); //this closes a modal dialog therefore is not generating a documentCompleted after! 
       semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); 
       //delete first in line 
       DeleteFirstMessageItem(doc, ref ele, ref obj, ref mi, ref tbody); //this closes a modal dialog therefore is not generating a documentCompleted after! 
       semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); 
      } 
     } 
     return new ActionResponse(true, archive); 
    } 

На практике этот метод принимает страницу из ММОРПГА и читает сообщения, отправленные в учетную запись другими игроками и сохраняющие их в классе ActionResponse с помощью метода ReadMessageEntry.

Помимо реализации и логики кода, которые действительно зависят от случая (и не полезны для вас), есть несколько интересных элементов, которые могут быть приятными для вас. я поставил некоторые комментарии в коде и выделены 3 важные моменты [с символами (1), (2) и (3)]

алго является:

1) Прибытие на страницу

2) получить базовый документ от WebBrowser

3) найти элемент, чтобы нажать, чтобы попасть на страницу сообщений [сделано с: HtmlElement ele = doc.GetElementById("message_alert_box");]

4) Запустите событие щелчка по нему с помощью экземпляра MethodInfo и обратного вызова [это вызывает другую страницу, так что документ будет завершен рано или поздно]

5) Подождите, пока документ будет называться, а затем протекают [сделано с: semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); в точке (1)]

6) Выборка нового документа из веб-браузера после того, как страница изменена

7) найти конкретный якорь на страницу, которая является определяющим, где сообщение я хотите прочитать:

8) Убедитесь, что такая TAG присутствует в (возможно, некоторые AJAX задерживают то, что я хочу читать, чтобы быть готовыми) [сделано с помощью: ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000), что является точкой (2)]

9) Проведите весь цикл для чтения каждого сообщения, что подразумевает открытие модальный диалог, который находится на одной странице, поэтому не генерирует DocumentCompleted, читает его, когда он готов, затем закрывает его и reloop. Для этого конкретного случая я использую перегрузку (1) называется semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); в точке (3)

Теперь три метода я использую, чтобы сделать паузу, проверить и прочитать:

(1) Для остановки во время DocumentCompleted поднимается без перезаряда метод DocumentCompleted, который может быть использован для более чем одной цели (как в вашем случае)

private bool WaitForDocumentCompleted() 
     { 
      Thread.SpinWait(1000); //This is dirty but working 
      while (Program.BrowsingSystem.IsBusy) //BrowsingSystem is another link to Browser that is made public in my Form and IsBusy is just a bool put to TRUE when Navigating event is raised and but to False when the DocumentCOmpleted is fired. 
      { 
       Application.DoEvents(); 
       Thread.SpinWait(1000); 
      } 

      if (Program.BrowsingSystem.IsInfoAvailable) //IsInfoAvailable is just a get property to cover webBroweser.Document inside a lock statement to protect from concurrent accesses. 
      { 
       return true; 
      } 
      else 
       return false; 
     } 

(2) подождите конкретного тега будет доступен на странице:

public static bool WaitForAvailability(this HtmlElement tag, string id, HtmlDocument documentToExtractFrom, long maxCycles) 
     { 
      bool cond = true; 
      long counter = 0; 
      while (cond) 
      { 
       Application.DoEvents(); //VERIFY trovare un modo per rimuovere questa porcheria 
       tag = documentToExtractFrom.GetElementById(id); 
       if (tag != null) 
        cond = false; 
       Thread.Yield(); 
       Thread.SpinWait(100000); 
       counter++; 
       if (counter > maxCycles) 
        return false; 
      } 
      return true; 
     } 

(3) Грязная трюка, ожидающая документального завершения, который когда-либо появится, потому что никакие кадры не нуждаются в перезагрузке на странице!

private bool WaitForDocumentCompleted(int seconds) 
    { 
     int counter = 0; 
     while (Program.BrowsingSystem.IsBusy) 
     { 
      Application.DoEvents(); 
      Thread.Sleep(1000); 
      if (counter == seconds) 
      { 
      return true; 
      } 
      counter++; 
     } 
     return true; 
    } 

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

private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
     { 
      if (Program.BrowsingSystem.BrowserLink.ReadyState == WebBrowserReadyState.Complete) 
      { 
       lock (Program.BrowsingSystem.BrowserLocker) 
       { 
        Program.BrowsingSystem.ActualPosition = Program.BrowsingSystem.UpdatePosition(Program.BrowsingSystem.Document); 
        Program.BrowsingSystem.CheckContentAvailability(); 
        Program.BrowsingSystem.IsBusy = false; 
       } 
      } 
     } 

private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e) 
     { 
      lock (Program.BrowsingSystem.BrowserLocker) 
      { 
       Program.BrowsingSystem.ActualPosition.PageName = OgamePages.OnChange; 
       Program.BrowsingSystem.IsBusy = true; 
      } 
     } 

Пожалуйста, дайте посмотреть here знать бардак за DoEvents(), если вы уже в курсе о деталях, которые лежат за реализацией представленного здесь (надеюсь, что это не является проблемой для ссылок на другие сайты из S. Переполнение).

Небольшая заключительная записка о том, что вам нужно поместить вызов метода Navigate в Invoke, когда вы используете его из экземпляра формы: это кристально ясно, что вам нужно вызвать, потому что методы, которые необходимо использовать webBrowser (или даже имеющий его в качестве реферируемой переменной) должен быть запущен в той же самой теме самого веб-браузера!

Кроме того, если WB является дочерним элементом какого-либо контейнера формы, ему также необходимо, чтобы поток из того, где он был создан, является тем же самым из создания Формы, и для транзитивности все методы, которые необходимо использовать для WB необходимо вызвать в потоке формы (в этом случае вызов переводит ваши вызовы в собственный поток Form). Надеюсь, это полезно для вас (я просто оставил комментарий // VERIFY в коде на моем родном языке, чтобы вы знали, что я думаю о Application.DoEvents()).

С наилучшими пожеланиями, Alex

Смежные вопросы