Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebBrowser Control in a new thread

I have a list Uri's that I want "clicked" To achieve this I"m trying to create a new web-browser control per Uri. I create a new thread per Uri. The problem I'm having is the thread end before the document is fully loaded, so I never get to make use of the DocumentComplete event. How can I overcome this?

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); } 
like image 531
Art W Avatar asked Nov 24 '10 17:11

Art W


2 Answers

You have to create an STA thread that pumps a message loop. That's the only hospitable environment for an ActiveX component like WebBrowser. You won't get the DocumentCompleted event otherwise. Some sample code:

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     } } 
like image 180
Hans Passant Avatar answered Sep 17 '22 13:09

Hans Passant


Here is how to organize a message loop on a non-UI thread, to run asynchronous tasks like WebBrowser automation. It uses async/await to provide the convenient linear code flow and loads a set of web pages in a loop. The code is a ready-to-run console app which is partially based on this excellent post.

Related answers:

  • https://stackoverflow.com/a/22262976/1768303
  • https://stackoverflow.com/a/21775343/1768303

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();             }         }     } } 
like image 38
noseratio Avatar answered Sep 17 '22 13:09

noseratio