การควบคุมเว็บเบราว์เซอร์ในเธรดใหม่


84

ฉันมีรายการ Uri ที่ฉันต้องการ "คลิก" เพื่อให้บรรลุสิ่งนี้ฉันพยายามสร้างการควบคุมเว็บเบราว์เซอร์ใหม่ต่อ Uri ฉันสร้างเธรดใหม่ต่อ Uri ปัญหาที่ฉันพบคือเธรดสิ้นสุดก่อนเอกสาร โหลดเต็มดังนั้นฉันจึงไม่สามารถใช้ประโยชน์จากเหตุการณ์ DocumentComplete ได้ฉันจะเอาชนะสิ่งนี้ได้อย่างไร

var item = new ParameterizedThreadStart(ClicIt.Click); 
var thread = new Thread(item) {Name = "ClickThread"}; 
thread.Start(uriItem);

public static void Click(object o)
{
    var url = ((UriItem)o);
    Console.WriteLine(@"Clicking: " + url.Link);
    var clicker = new WebBrowser { ScriptErrorsSuppressed = true };
    clicker.DocumentCompleted += BrowseComplete;
    if (String.IsNullOrEmpty(url.Link)) return;
    if (url.Link.Equals("about:blank")) return;
    if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://"))
        url.Link = "http://" + url.Link;
    clicker.Navigate(url.Link);
}

คำตอบ:


152

คุณต้องสร้างเธรด STA ที่ปั๊มวนข้อความ นั่นเป็นสภาพแวดล้อมที่เอื้ออำนวยเพียงอย่างเดียวสำหรับส่วนประกอบ ActiveX เช่น WebBrowser คุณจะไม่ได้รับเหตุการณ์ DocumentCompleted เป็นอย่างอื่น โค้ดตัวอย่างบางส่วน:

private void runBrowserThread(Uri url) {
    var th = new Thread(() => {
        var br = new WebBrowser();
        br.DocumentCompleted += browser_DocumentCompleted;
        br.Navigate(url);
        Application.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
    var br = sender as WebBrowser;
    if (br.Url == e.Url) {
        Console.WriteLine("Natigated to {0}", e.Url);
        Application.ExitThread();   // Stops the thread
    }
}

8
ใช่ เพียงเพิ่ม System.Windows.Forms บันทึกวันของฉันด้วย ขอบคุณ
zee

4
ฉันกำลังพยายามปรับรหัสนี้ให้เข้ากับสถานการณ์ของฉัน ฉันต้องรักษาWebBrowserวัตถุให้คงอยู่ (เพื่อบันทึกสถานะ / คุกกี้ ฯลฯ ) และทำการNavigate()โทรหลายครั้งในช่วงเวลาหนึ่ง แต่ฉันไม่แน่ใจว่าจะApplication.Run()โทรออกที่ไหนเพราะมันบล็อกโค้ดเพิ่มเติมจากการเรียก เบาะแสใด ๆ ?
dotNET

คุณสามารถโทรApplication.Exit();แจ้งให้Application.Run()กลับ
Mike de Klerk

26

ต่อไปนี้เป็นวิธีจัดระเบียบวนข้อความบนเธรดที่ไม่ใช่ UI เพื่อรันงานแบบอะซิงโครนัสเช่นWebBrowserระบบอัตโนมัติ ใช้async/awaitเพื่อจัดเตรียมโฟลว์โค้ดเชิงเส้นที่สะดวกและโหลดชุดหน้าเว็บแบบวนซ้ำ โค้ดนี้เป็นแอปคอนโซลที่พร้อมรันซึ่งบางส่วนอ้างอิงจากโพสต์ที่ยอดเยี่ยมนี้

คำตอบที่เกี่ยวข้อง:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConsoleApplicationWebBrowser
{
    // by Noseratio - https://stackoverflow.com/users/1768303/noseratio
    class Program
    {
        // Entry Point of the console app
        static void Main(string[] args)
        {
            try
            {
                // download each page and dump the content
                var task = MessageLoopWorker.Run(DoWorkAsync,
                    "http://www.example.com", "http://www.example.net", "http://www.example.org");
                task.Wait();
                Console.WriteLine("DoWorkAsync completed.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("DoWorkAsync failed: " + ex.Message);
            }

            Console.WriteLine("Press Enter to exit.");
            Console.ReadLine();
        }

        // navigate WebBrowser to the list of urls in a loop
        static async Task<object> DoWorkAsync(object[] args)
        {
            Console.WriteLine("Start working.");

            using (var wb = new WebBrowser())
            {
                wb.ScriptErrorsSuppressed = true;

                TaskCompletionSource<bool> tcs = null;
                WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
                    tcs.TrySetResult(true);

                // navigate to each URL in the list
                foreach (var url in args)
                {
                    tcs = new TaskCompletionSource<bool>();
                    wb.DocumentCompleted += documentCompletedHandler;
                    try
                    {
                        wb.Navigate(url.ToString());
                        // await for DocumentCompleted
                        await tcs.Task;
                    }
                    finally
                    {
                        wb.DocumentCompleted -= documentCompletedHandler;
                    }
                    // the DOM is ready
                    Console.WriteLine(url.ToString());
                    Console.WriteLine(wb.Document.Body.OuterHtml);
                }
            }

            Console.WriteLine("End working.");
            return null;
        }

    }

    // a helper class to start the message loop and execute an asynchronous task
    public static class MessageLoopWorker
    {
        public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
        {
            var tcs = new TaskCompletionSource<object>();

            var thread = new Thread(() =>
            {
                EventHandler idleHandler = null;

                idleHandler = async (s, e) =>
                {
                    // handle Application.Idle just once
                    Application.Idle -= idleHandler;

                    // return to the message loop
                    await Task.Yield();

                    // and continue asynchronously
                    // propogate the result or exception
                    try
                    {
                        var result = await worker(args);
                        tcs.SetResult(result);
                    }
                    catch (Exception ex)
                    {
                        tcs.SetException(ex);
                    }

                    // signal to exit the message loop
                    // Application.Run will exit at this point
                    Application.ExitThread();
                };

                // handle Application.Idle just once
                // to make sure we're inside the message loop
                // and SynchronizationContext has been correctly installed
                Application.Idle += idleHandler;
                Application.Run();
            });

            // set STA model for the new thread
            thread.SetApartmentState(ApartmentState.STA);

            // start the thread and await for the task
            thread.Start();
            try
            {
                return await tcs.Task;
            }
            finally
            {
                thread.Join();
            }
        }
    }
}

1
ขอบคุณสำหรับคำตอบที่ยอดเยี่ยมและให้ข้อมูล! มันคือสิ่งที่ฉันกำลังมองหา อย่างไรก็ตามดูเหมือนว่าคุณจะ (จงใจ?) ใส่คำสั่ง Dispose () ผิด
wodzu

@ Pawełคุณพูดถูกรหัสนั้นไม่ได้คอมไพล์ :) ฉันคิดว่าวางเวอร์ชันผิดแก้ไขแล้ว ขอบคุณสำหรับการระบุสิ่งนี้ คุณอาจต้องการตรวจสอบวิธีการทั่วไปเพิ่มเติม: stackoverflow.com/a/22262976/1768303
noseratio

ผมพยายามที่จะเรียกใช้รหัสนี้ task.Wait();แต่จะได้รับในการติด ฉันทำอะไรผิด?
0014

1
สวัสดีคุณอาจช่วยฉันในเรื่องนี้ได้: stackoverflow.com/questions/41533997/… - วิธีนี้ใช้ได้ดี แต่ถ้า Form ถูกสร้างอินสแตนซ์ก่อน MessageLoopWorker มันจะหยุดทำงาน
Alex Netkachov

3

จากประสบการณ์ของฉันในอดีตเว็บเบราว์เซอร์ไม่ชอบปฏิบัติการนอกเธรดแอปพลิเคชันหลัก

ลองใช้ httpwebrequests แทนคุณสามารถตั้งค่าเป็นแบบอะซิงโครนัสและสร้างตัวจัดการสำหรับการตอบสนองเพื่อให้ทราบเมื่อประสบความสำเร็จ:

วิธีการใช้ httpwebrequest-net-asynchronously


ปัญหาของฉันคือสิ่งนี้ การคลิก Uri จำเป็นต้องให้ไซต์เข้าสู่ระบบฉันไม่สามารถทำสิ่งนี้ได้ด้วย WebRequest โดยการใช้เว็บเบราว์เซอร์จะใช้แคช IE อยู่แล้วไซต์ต่างๆจึงเข้าสู่ระบบมีวิธีแก้ปัญหานั้นหรือไม่? ลิงก์เกี่ยวข้องกับ facebook ฉันสามารถเข้าสู่ระบบ facebook และคลิกที่ลิงค์กับ webwrequest ได้หรือไม่?
Art W

@ArtW ฉันรู้ว่านี่เป็นความคิดเห็นเก่า แต่ผู้คนสามารถแก้ปัญหานั้นได้โดยการตั้งค่าwebRequest.Credentials = CredentialsCache.DefaultCredentials;
vapcguy

@vapcguy ถ้าเป็น API ก็ใช่ แต่ถ้าเป็นเว็บไซต์ที่มีองค์ประกอบ HTML ในการเข้าสู่ระบบก็จะต้องใช้คุกกี้หรือแคชของ IE มิฉะนั้นลูกค้าจะไม่รู้ว่าจะทำอย่างไรกับCredentialsคุณสมบัติของวัตถุและวิธีการเติม HTML
ColinM

@ColinM บริบททั้งหน้านี้กำลังพูดถึงคือการใช้วัตถุ HttpWebRequest และ C # .NET ไม่ใช่ HTML ธรรมดาและองค์ประกอบแบบฟอร์มที่โพสต์เช่นเดียวกับที่คุณทำกับ JavaScript / AJAX แต่ไม่ว่าคุณจะมีเครื่องรับ และสำหรับการเข้าสู่ระบบคุณควรใช้ Windows Authentication และ IIS จะจัดการสิ่งนี้โดยอัตโนมัติ หากคุณต้องการทดสอบด้วยตนเองคุณสามารถใช้ได้WindowsIdentity.GetCurrent().Nameหลังจากใช้การแอบอ้างบุคคลอื่นและทดสอบกับการค้นหา AD หากต้องการ ไม่แน่ใจว่าจะใช้คุกกี้และแคชอย่างไร
vapcguy

@vapcguy คำถามกำลังพูดถึงWebBrowserสิ่งที่บ่งบอกว่ากำลังโหลดหน้า HTML OP ยังบอกว่าWebRequestจะไม่บรรลุสิ่งที่เขาต้องการดังนั้นหากเว็บไซต์ต้องการอินพุต HTML สำหรับการเข้าสู่ระบบการตั้งค่าCredentialsวัตถุจะไม่ทำงาน นอกจากนี้ตามที่ OP กล่าวไว้ไซต์ดังกล่าวรวมถึง Facebook ด้วย การรับรองความถูกต้องของ Windows จะไม่ทำงานกับสิ่งนี้
ColinM

0

วิธีแก้ปัญหาง่ายๆที่การทำงานพร้อมกันของเว็บเบราว์เซอร์หลายตัวเกิดขึ้น

  1. สร้างแอปพลิเคชัน Windows Forms ใหม่
  2. วางปุ่มชื่อ button1
  3. วางกล่องข้อความชื่อ textBox1
  4. ตั้งค่าคุณสมบัติของช่องข้อความ: Multiline true และ ScrollBars Both
  5. เขียนปุ่มต่อไปนี้ตัวจัดการคลิก 1 คลิก:

    textBox1.Clear();
    textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
    int completed_count = 0;
    int count = 10;
    for (int i = 0; i < count; i++)
    {
        int tmp = i;
        this.BeginInvoke(new Action(() =>
        {
            var wb = new WebBrowser();
            wb.ScriptErrorsSuppressed = true;
            wb.DocumentCompleted += (cur_sender, cur_e) =>
            {
                var cur_wb = cur_sender as WebBrowser;
                if (cur_wb.Url == cur_e.Url)
                {
                    textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine);
                    completed_count++;
                }
            };
            wb.Navigate("/programming/4269800/webbrowser-control-in-a-new-thread");
        }
        ));
    }
    
    while (completed_count != count)
    {
        Application.DoEvents();
        Thread.Sleep(10);
    }
    textBox1.AppendText("All completed" + Environment.NewLine);
    
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.