2010-11-24 20 views
70

Tengo una lista de Uri que quiero "hacer clic" Para lograr esto estoy tratando de crear un nuevo control de navegador web por Uri. Creo un nuevo hilo por Uri. El problema es que ' m teniendo es el extremo del hilo antes de que el documento se ha cargado completamente, por lo que nunca llegue a hacer uso del evento documento completo. ¿Cómo puedo superar esto?WebBrowser Control en un nuevo hilo

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

Respuesta

132

Tienes que crear un subproceso STA que bombea un bucle de mensajes. Ese es el único entorno hospitalario para un componente ActiveX como WebBrowser. De lo contrario, no obtendrá el evento DocumentCompleted. Un código de ejemplo:

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 
    } 
} 
+0

¿Podría hacerse esto dentro de una aplicación WPF? – Para

+6

¡Sí! Simplemente agregue System.Windows.Forms. Me salvó el día, también. Gracias – zee

+2

Estoy tratando de adaptar este código a mi situación. Tengo que mantener vivo el objeto 'WebBrowser' (para guardar el estado/cookies, etc.) y realizar múltiples llamadas' Navigate() 'a lo largo del tiempo. Pero no estoy seguro de dónde colocar mi llamada 'Application.Run()', ya que bloquea la ejecución de más código. ¿Alguna pista? – dotNET

2

Desde mi experiencia en el pasado, el navegador web no le gusta operativo fuera del subproceso de la aplicación principal

Intente utilizar httpwebrequests en su lugar, puede establecerlos como asincrónicos y crear un controlador para la respuesta para saber cuándo es suc cesfull:

how-to-use-httpwebrequest-net-asynchronously

+0

Mi problema con eso es esto. El URI al que se hizo clic requirió que el sitio estuviera conectado. No puedo lograrlo con WebRequest. Al usar WebBrowser, ya utiliza el caché de IE, por lo que los sitios inician sesión. ¿Hay alguna forma de evitarlo? Los enlaces implican facebook. Entonces, ¿puedo iniciar sesión en Facebook y hacer clic en el enlace con webwrequest? –

22

Aquí es cómo organizar un bucle de mensajes en un hilo no interfaz de usuario, para ejecutar tareas asíncronas como WebBrowser automatización. Utiliza async/await para proporcionar el flujo de código lineal conveniente y carga un conjunto de páginas web en un bucle. El código es una aplicación de consola lista para ejecutar que se basa parcialmente en this excellent post.

respuestas relacionadas:

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

¡Gracias por su brillante e informativa respuesta! Es exactamente lo que estaba buscando. Sin embargo, parece que (¿intencionalmente?) Extravió la declaración Dispose(). – wodzu

+0

@ Paweł, tienes razón, ese código ni siquiera se compiló :) Creo que pegué una versión incorrecta, ahora corregida. Gracias por descubrir esto Es posible que desee comprobar un enfoque más genérico: http://stackoverflow.com/a/22262976/1768303 – Noseratio

+0

He intentado ejecutar este código, sin embargo, se bloquea en 'task.Wait();'. Estoy haciendo algo mal ? – 0014

0

Una solución simple a la que la operación simultánea de varios navegadores web se produce

  1. Crear una nueva aplicación de Windows Forms
  2. Coloque el botón denominado botón1
  3. Coloque el cuadro de texto denominado textBox1
  4. Establecer las propiedades de campo de texto : Multilínea verdadera y barras de desplazamiento Ambos
  5. escribir el siguiendo button1 click handler:

    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("https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread"); 
        } 
        )); 
    } 
    
    while (completed_count != count) 
    { 
        Application.DoEvents(); 
        Thread.Sleep(10); 
    } 
    textBox1.AppendText("All completed" + Environment.NewLine);