3

Web サイトをナビゲートして、Windows フォームの WebBrowser コントロールを使用してプログラムでページを操作しようとしています。WebBrowser の DocumentCompleted イベントがトリガーされるまでスレッドをブロックする方法を探しているときに、これを見つけました。それを考えると、ここに私の現在のコードがあります:

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() が呼び出されているのと同じスレッドを使用しているためです。 )。

興味深いのは、ツールボックスから WebBrowser コントロールをフォームに追加し (ドラッグ アンド ドロップ)、それを使用して移動すると、このコードは完全に機能することです (Navigate への呼び出しを Invoke への呼び出し内に配置する以外に変更はありません -下記参照)。しかし、デザイナー ファイルに WebBrowser コントロールを手動で追加しても、機能しません。また、フォームに WebBrowser を表示する必要はありません。結果を報告したいだけです。

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

私の質問は、次のとおりです。ブラウザーの DocumentCompleted イベントが発生するまでスレッドを中断する最善の方法は何ですか?

4

3 に答える 3

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

実際には、このメソッドは MMORPG のページを取得し、他のプレイヤーによってアカウントに送信されたメッセージを読み取り、メソッド ReadMessageEntry を介して ActionResponse クラスに格納します。

実装と、実際にはケースに依存する (そしてユーザーにとっては役に立たない) コードのロジックとは別に、ユーザーのケースに注意するとよい興味深い要素がいくつかあります。コードにいくつかのコメントを入れて、3 つの重要なポイントを強調しました [記号(1),(2)(3)]

アルゴリズムは次のとおりです。

1) ページに到着

2) webBrowser から基になるドキュメントを取得する

3) クリックしてメッセージページに移動する要素を見つける [: で完了HtmlElement ele = doc.GetElementById("message_alert_box");]

4) MethodInfo インスタンスとリフレクションごとの呼び出しを介してクリックのイベントをトリガーします [これは別のページを呼び出すため、遅かれ早かれ DocumentCompleted が到着します]

5) 完了したドキュメントが呼び出されるのを待ってから続行します [完了:semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); ポイント (1) で]

6) ページが変更された後、webBrowser から新しいドキュメントをフェッチします。

7) 読みたいメッセージがどこにあるかを定義しているページ上の特定のアンカーを見つける

8)そのようなタグがページに存在することを確認してください(AJAXが読み取りたいものの準備を遅らせる可能性があるため)[完了:ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000)それがポイント(2)です]

9) 各メッセージを読み取るためのループ全体を実行します。これは、同じページにあるモーダル ダイアログ フォームを開くことを意味するため、DocumentCompleted を生成せず、準備ができたら読み取り、閉じて、再ループします。この特定のケースではsemaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);、ポイント (3) で呼び出される (1) のオーバーロードを使用します。

ここで、一時停止、確認、および読み取りに使用する 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) ページ上でフレームをリロードする必要がないため、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.Overflow から他のサイトにリンクすることが問題にならないことを願っています)。

Form インスタンスから Navigate メソッドを使用する場合、Invoke 内に Navigate メソッドの呼び出しを配置する必要があるという事実についての最後の小さな注意: これは、webBrowser (または webBrowser (または参照変数としてスコープに入れても) webBrowser 自体の同じスレッドで起動する必要があります!

さらに、WB が何らかのフォーム コンテナの子である場合、インスタンス化されるスレッドがフォームの作成と同じである必要があり、推移性のために、WB で動作する必要があるすべてのメソッドを呼び出す必要があります。フォームスレッドで(あなたの場合、呼び出しはフォームネイティブスレッドで呼び出しを再配置します)。これがお役に立てば幸いです (Application.DoEvents() について私が考えていることをお知らせするために、母国語のコードに //VERIFY コメントを残しました)。

敬具、アレックス

于 2012-07-06T19:42:07.450 に答える
0

ハァッ!同じ質問がありました。これは、イベント処理を使用して行うことができます。ページの途中でスレッドを停止すると、ページが終了するまで待つ必要があります。アタッチすることで簡単にできます

 Page.LoadComplete += new EventHandler(triggerFunction);

triggerFunctionでこれを行うことができます

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

これが機能するかどうか教えてください。私は結局、私のスレッドを使用せず、代わりにそれらをtriggerFunctionに入れるだけでした。頭のてっぺんから答えているので、一部の構文は100%正しくない可能性があります

于 2012-07-03T19:04:23.833 に答える
0

編集

同じメソッドではなく、このようにコンポーネントの初期化メソッドに登録します。

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

DocumentCompleted イベントでチェックすると、ReadyState はドキュメントの読み込みの進行状況を通知します。

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

}
}
于 2012-07-03T19:07:24.380 に答える