2012-07-03 2 views
4

웹 사이트를 탐색하고 Windows Form의 WebBrowser 컨트롤을 사용하여 프로그래밍 방식으로 페이지에서 일부 작업을 수행하려고합니다. WebBrowser의 DocumentCompleted 이벤트가 트리거 될 때까지 내 스레드를 차단하는 방법을 찾고있는 동안 this을 찾았습니다. 여기에 내 현재 코드의 것을 감안할 때 :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 컨트롤을 추가 한 다음이 코드를 사용하여 탐색하면이 코드가 완벽하게 작동한다는 것입니다. 호출 - 아래 참조). 그러나 Designer 파일에 WebBrowser 컨트롤을 수동으로 추가하면 작동하지 않습니다. 그리고 나는 폼에 가시적 인 WebBrowser를 정말로 원하지 않습니다. 단지 결과를보고 싶습니다.

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

내 질문에, 다음입니다 : 브라우저의 DocumentCompleted 이벤트가 발생 될 때까지 스레드를 일시 중단하는 가장 좋은 방법은 무엇입니까?

답변

0

HAH! 나는 같은 질문을했다. 이벤트 처리를 통해이 작업을 수행 할 수 있습니다. 페이지 중간에 스레드를 중지하면 페이지가 끝날 때까지 기다려야합니다. 당신은 쉽게 당신이

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

이 작동하는지 알려줘 할 수있는 triggerFunction에서

Page.LoadComplete += new EventHandler(triggerFunction); 

를 부착하여이 작업을 수행 할 수 있습니다. 나는 내 스레드를 사용하지 않고 대신 triggerFunction에 물건을 넣었습니다. 내가 대신 같은 방법으로,이 같은 초기화 컴포넌트 메소드 내 머리

+0

이 내가 autoResetEvent.Set()를 직접 호출 할 것을 제외하고 내가 대신 중간체를 사용하는 (autoResetEvent.Reset를 (계속 차단 해제되지 않습니다)), 무엇을 기본적으로 triggerFunction. 그러나이 코드는 작동하지 않습니다. (다시 말하지만 EventHandler가 초기 호출과 동일한 스레드에서 실행되므로 스레드가 무기한 차단되면 이벤트가 실행되지 않습니다.) – Chris

+0

메인 페이지 스레드가 실행을 끝내지 못하게하는 데 문제가있는 것 같습니다. 스레드를 일시 중단하는 경우 페이지는 렌더링을 계속 유지해야합니다 (스레드가됩니다). 어쩌면 잘못된 함수 호출을 보여 줬을 까? 그러나 페이지가 렌더링을 완료했음을 보장하기 때문에 이벤트 트리거에 스레드를 해제하려는 경우 (적어도 서버에서 생각하는 것처럼) –

+0

DocumentCompleted 이벤트 처리를보다 명확하게하기 위해 게시물을 편집했습니다. 분명히 스레드가 실행되는 방식에 문제가 있지만 분명히 알 수는 없습니다. – Chris

0

편집

레지스터의 상단을 응답하고 있기 때문에 일부 구문은 100 % 정확하지 않을 수 있습니다.

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

앞서 언급했듯이 Navigate() 호출과 동일한 스레드에서 실행되므로 WaitOne()이 반환 한 후에 만 ​​실행되기 때문에 DocumentCompleted 이벤트는 실행되지 않습니다 (Set() 메서드 호출 이후 결코 발생하지 않음) 결코 만들어지지 않는다). – Chris

1

크리스,

나는 여기에 문제를 해결하지만 얼굴 모든 것이 내가 기대 한대로 작동하기 전에 해결해야했다 그 아래에 아래에 설명 한 번 봐주세요 가능한 구현을 전달합니다.여기 웹 브라우저의 페이지에 대한 몇 가지 활동을하는 방법의 예합니다 (웹 브라우저 내 경우에는 양식의 일부임을 유의) :이 방법은 MMORPG의 페이지를 받아서 읽고 실제로

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); 
    } 

다른 플레이어가 계정에 보낸 메시지를 읽고 ReadMessageEntry 메서드를 통해 ActionResponse 클래스에 저장합니다.

실제로 대소 문자를 구별하고 유용하지 않은 코드의 구현과 논리를 제외하면 흥미로운 요소가 몇 가지 있습니다.

1) 페이지

2) 기본 문서를 얻을 수에 도착 : 나는

너 한테은 [문자 (1), (2)(3)로] 3 중요한 점을 코드에서 몇 가지 의견을 넣어 강조

다음 웹 브라우저

3) 메시지 페이지로 이동하기 위해 클릭 할 수있는 요소를 찾을에서 [HtmlElement ele = doc.GetElementById("message_alert_box");으로 수행] 4) MethodInfo 인스턴스 및 반사 식 호출을 통해 클릭 이벤트 발생 [DocumentCompleted가 조만간 도착하도록 다른 페이지를 호출 함]

5) 완료된 문서가 수신 될 때까지 기다린 후 [으로 수행 : 시점 semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); (1)]하여 진행하는

6) 페이지

7

을 변경 후) 웹 브라우저에서 새로운 문서를 페치 정의 된 페이지상의 특정 앵커를 찾을 위치를 메시지 I 읽으려는 경우

8) 해당 태그가 페이지 (일부 AJAX가 준비가되기 위해 읽고 싶은 것을 지연시킬 수 있기 때문에) [done : ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000) 즉 point (2)]

9) 각 메시지를 읽기 위해 전체 루프를 수행합니다. 모달 대화 상자 양식은 동일한 페이지에 있으므로 DocumentCompleted를 생성하지 않습니다. 준비가되면 읽을 수 있도록 한 다음 닫고 다시 실행하십시오. 이 특별한 경우를 들어 I (1) 지점에서 semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);라고의 오버로드를 사용 (3)

은 이제 일시 중지 확인하는 데 사용하는 세 가지 방법을 읽어

(1) DocumentCompleted없이 발생하면서 중지하려면

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; 
     } 
: (귀하의 경우로) 이상의 단일 목적을 위해 이용 될 수있다 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) 페이지에서 사용할 수 있도록 특정 태그 기다립니다 과충전

(3) 프레임에 페이지를 다시로드 할 필요가 없어 DocumentCompleted를 기다리는 더러운 트릭!

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 Methods와 Navigating을 전달하여 사용 방법에 대한 전체 그림을 제공합니다.

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; 
      } 
     } 

DoEvents()는 여기에 제시된 구현 뒤에 거짓말 세부 사항에 대해 지금 알고있는 경우 (이 S.에서 다른 사이트를 연결하는 문제가되지 않습니다 희망 뒤에 혼란을 알 수있는 모습 here을주십시오 과다).

Form 인스턴스에서 Navigate 메서드를 호출 할 때 호출을 Invoke 내부에 넣어야한다는 사실에 대한 마지막 작은 메모입니다.이 메서드는 작동해야하는 메서드가 있기 때문에 Invoke가 필요합니다. webBrowser (또는 심지어 refereed 변수로 범위에 포함)는 webBrowser 자체의 동일한 Thread에서 실행되어야합니다.

또한 WB가 일종의 Form 컨테이너의 하위 항목 인 경우 인스턴스 생성 위치의 스레드가 양식 작성과 동일해야하며 과도기 때문에 WB에서 작업해야하는 모든 방법 Form 스레드에서 호출해야합니다 (호출이 Form 네이티브 스레드에서 호출을 재배치). 이것이 유용하다고 생각합니다. (방금 Application.DoEvents()에 대해 어떻게 생각하는지 알려주기 위해 내 모국어로 된 코드에 // VERIFY 주석을 남겼습니다.)

종류의 안부 알렉스

관련 문제