WebBrowser Control in a new thread
C#MultithreadingBrowserC# Problem Overview
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);
}
C# Solutions
Solution 1 - C#
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
}
}
Solution 2 - C#
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/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(); } } }
}
Solution 3 - C#
From my experience in the past the webbrowser does not like operating outside of the main application thread.
Try using httpwebrequests instead, you can set them as asynchronous and create a handler for the response to know when it is succesfull:
Solution 4 - C#
A simple solution at which the simultaneous operation of several WebBrowsers occurs
-
Create a new Windows Forms application
-
Place the button named button1
-
Place the text box named textBox1
-
Set properties of text field: Multiline true and ScrollBars Both
-
Write the following 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);