Yeni bir iş parçacığında WebBrowser Kontrolü


84

"Tıklanmasını" istediğim bir Uri listesi var Bunu başarmak için URI başına yeni bir web tarayıcısı denetimi oluşturmaya çalışıyorum. URI başına yeni bir iş parçacığı oluşturuyorum. Yaşadığım sorun belgeden önce iş parçacığı sonudur tamamen yüklendi, bu yüzden DocumentComplete olayını asla kullanamıyorum Bunun üstesinden nasıl gelebilirim?

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

Yanıtlar:


152

Bir mesaj döngüsü pompalayan bir STA iş parçacığı oluşturmanız gerekir. WebBrowser gibi bir ActiveX bileşeni için tek misafirperver ortam budur. Aksi takdirde DocumentCompleted olayını alamazsınız. Bazı örnek kodlar:

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
Evet! System.Windows.Forms'u eklemeniz yeterlidir. Günümü de kurtardım. Teşekkürler
zee

4
Bu kodu durumuma uyarlamaya çalışıyorum. WebBrowserNesneyi canlı tutmalı (durumu / çerezleri vb. Kaydetmek için) ve Navigate()zaman içinde birden fazla arama yapmalıyım . Ancak Application.Run()çağrımı nereye yapacağımdan emin değilim , çünkü daha fazla kodun yürütülmesini engelliyor. Herhangi bir ipucu?
dotNET

Geri dönmesine Application.Exit();izin vermek için arayabilirsin Application.Run().
Mike de Klerk

26

WebBrowserOtomasyon gibi eşzamansız görevleri çalıştırmak için UI olmayan bir iş parçacığında bir mesaj döngüsünü nasıl organize edeceğiniz aşağıda açıklanmıştır . async/awaitUygun doğrusal kod akışı sağlamak için kullanır ve bir döngüdeki bir dizi web sayfasını yükler. Kod, kısmen bu mükemmel gönderiye dayanan, çalışmaya hazır bir konsol uygulamasıdır .

İlgili yanıtlar:

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
Bu parlak ve bilgilendirici cevap için teşekkürler! Tam olarak aradığım şey buydu. Ancak Dispose () ifadesini (kasıtlı olarak) yanlış yerleştirmiş görünüyorsunuz.
wodzu

@ Paweł, haklısın, bu kod bile derlenmedi :) Sanırım yanlış bir sürüm yapıştırıldı, şimdi düzeltildi. Bunu anladığınız için teşekkürler. Daha genel bir yaklaşımı kontrol etmek isteyebilirsiniz: stackoverflow.com/a/22262976/1768303
noseratio

Bu kodu çalıştırmayı denedim, ancak takılı kalıyor task.Wait();. Yanlış bir şey mi yapıyorum
0014

1
Merhaba, belki bu konuda bana yardım edebilirsin: stackoverflow.com/questions/41533997/… - yöntem iyi çalışıyor, ancak Form MessageLoopWorker'dan önce başlatılmışsa çalışmayı durdurur.
Alex Netkachov

3

Geçmişteki tecrübelerime göre, web tarayıcısı ana uygulama iş parçacığı dışında çalışmayı sevmiyor.

Bunun yerine httpweb isteklerini kullanmayı deneyin, bunları eşzamansız olarak ayarlayabilir ve yanıtın ne zaman başarılı olduğunu bilmesi için bir işleyici oluşturabilirsiniz:

nasıl-kullanılır-httpwebrequest-net-eşzamansız olarak


Bununla ilgili sorunum şu. Tıklanan URI, sitenin oturum açmasını gerektirdi. Bunu WebRequest ile başaramıyorum. Web Tarayıcısını kullanarak, IE önbelleğini zaten kullanır, bu nedenle siteler oturum açtı. Bunun bir yolu var mı? Bağlantılar facebook'u içerir. Facebook'ta oturum açıp webwrequest bağlantısını tıklayabilir miyim?
Art W

@ArtW Bunun eski bir yorum olduğunu biliyorum, ancak insanlar muhtemelen bunu ayarlayarak çözebilirlerwebRequest.Credentials = CredentialsCache.DefaultCredentials;
vapcguy

@vapcguy Bir API ise evet, ancak oturum açmak için HTML öğeleri olan bir web sitesiyse, IE çerezleri veya önbellek kullanması gerekir, aksi takdirde müşteri Credentialsobject özelliği ile ne yapacağını ve nasıl doldurulacağını bilemez HTML.
ColinM

@ColinM Bu sayfanın bahsettiği bağlam, JavaScript / AJAX ile yapabileceğiniz gibi, gönderilen basit HTML ve form öğelerini değil, HttpWebRequest nesnesini ve C # .NET'i kullanmaktır. Ama ne olursa olsun, bir alıcınız var. Ve oturum açmak için Windows Kimlik Doğrulaması kullanmanız gerekir ve IIS bunu yine de otomatik olarak halleder. Bunları manuel olarak test etmeniz gerekiyorsa WindowsIdentity.GetCurrent().Namekimliğe bürünmeyi uyguladıktan sonra kullanabilir ve isterseniz bir AD aramasına karşı test edebilirsiniz. Bunların herhangi biri için çerezlerin ve önbelleğin nasıl kullanılacağından emin değilim.
vapcguy

@vapcguy Soru, WebBrowserhangisinin HTML sayfalarının yüklendiğini göstereceğinden bahsediyor , OP WebRequestistediğini başaramayacağını bile söyledi , bu nedenle bir web sitesi oturum açma için HTML girdisi beklerse, Credentialsnesneyi ayarlamak işe yaramaz. Ek olarak, OP'nin dediği gibi, siteler arasında Facebook; Windows kimlik doğrulaması bunun üzerinde çalışmayacaktır.
ColinM

0

Birkaç Web Tarayıcısının eşzamanlı çalışmasının gerçekleştiği basit bir çözüm

  1. Yeni bir Windows Forms uygulaması oluşturun
  2. Button1 adlı düğmeyi yerleştirin
  3. TextBox1 adlı metin kutusunu yerleştirin
  4. Metin alanının özelliklerini ayarlayın: Çok satırlı true ve ScrollBars Both
  5. Aşağıdaki button1 tıklama işleyicisini yazın:

    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);
    
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.