Problem Scope:
I'm writing an aplication to save the HTML's retrieved from the Bing and Google searches. I know there are classes to execute the Web Requests using stream such as this example, but since Google and Bing both use Javascript and Ajax to render the results into the HTML, there's no way i can simply read the stream and use get to the result i need.
The solution to this, is to use the WebBrowser class and navigate to the url i want, so that the Browser itself will handle all the Javascript and Ajax scripting executions.
MultiThreading:
In order to make it more efficient, i have the same Form aplication firing a thread for each service (one for Bing, and one for Google).
Problem:
Since i need the WebBrowser, i have instantiated one for each thread (which are 2, at this moment). According to Microsoft, there is a known bug that prevents the DocumentCompleted event from firing if the WebBrowser is not visible and is not added to a visible form aswell (for more information, follow this link).
Real Problem:
The main issue is that, the DocumentCompleted event of the browser, never fires. Never.
I have wrote a proper handler for the DocumentCompleted event that never gets the callback. For handling the wait needed for the Browser event to fire, i have implemented a AutoResetEvent with a high timeout (5 minutes), that will dispose the webbrowser thread if it does not fire the event i need after 5 minutes.
At the moment, i have the Browser created and added into a WindowsForm, both are visible, and the event is still not firing.
Some Code:
// Creating Browser Instance
browser = new WebBrowser ();
// Setting up Custom Handler to "Document Completed" Event
browser.DocumentCompleted += DocumentCompletedEvent;
// Setting Up Random Form
genericForm = new Form();
genericForm.Width = 200;
genericForm.Height = 200;
genericForm.Controls.Add (browser);
browser.Visible = true;
As for the Navigation i have the Following (method for the browser) :
public void NavigateTo (string url)
{
CompletedNavigation = false;
if (browser.ReadyState == WebBrowserReadyState.Loading) return;
genericForm.Show (); // Shows the form so that it is visible at the time the browser navigates
browser.Navigate (url);
}
And, for the call of the Navigation i have this :
// Loading URL
browser.NavigateTo(URL);
// Waiting for Our Event To Fire
if (_event.WaitOne (_timeout))
{
// Success
}
{ // Error / Timeout From the AutoResetEvent }
TL:DR:
My WebBrowser is instantiated into a another STAThread, added to a form, both are visible and shown when the Browser Navigation fires, but the DocumentCompleted event from the Browser is never fired, so the AutoResetEvent always times out and i have no response from the browser.
Thanks in Advance and sorry for the long post
Although this seems a strange way, here is my attempt.
var tasks = new Task<string>[]
{
new MyDownloader().Download("http://www.stackoverflow.com"),
new MyDownloader().Download("http://www.google.com")
};
Task.WaitAll(tasks);
Console.WriteLine(tasks[0].Result);
Console.WriteLine(tasks[1].Result);
public class MyDownloader
{
WebBrowser _wb;
TaskCompletionSource<string> _tcs;
ApplicationContext _ctx;
public Task<string> Download(string url)
{
_tcs = new TaskCompletionSource<string>();
var t = new Thread(()=>
{
_wb = new WebBrowser();
_wb.ScriptErrorsSuppressed = true;
_wb.DocumentCompleted += _wb_DocumentCompleted;
_wb.Navigate(url);
_ctx = new ApplicationContext();
Application.Run(_ctx);
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
return _tcs.Task;
}
void _wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//_tcs.TrySetResult(_wb.DocumentText);
_tcs.TrySetResult(_wb.DocumentTitle);
_ctx.ExitThread();
}
}
Related
I have a foreach loop to call _webView.NavigateToLocalStreamUri(url[index], resolver) many times. As I know each time I navigate to a specific URL, the events NavigationStarting and NavigationCompleted are triggered. But in my case, NavigationStarting is called for every request but NavigationCompleted only trigger for the last request.
I want to know Is there a way to make sure NavigationCompleted is called for every request. I searched but there is no answer meet my purpose.
for (int i = 0; i < 3; i++){
// here I raise event to call `NavigateToLocalStreamUri` of _webview
}
_webview have 2 events NavigationStarting and NavigationCompleted
// when i = 0 or 1
the program only calls event handler of NavigationStarting, but not call event handler of NavigationCompleted
// when i = 2 (always, the last iteration of for loop)
the program calls event handler of both events NavigationStarting and NavigationCompleted
// Is there any solution to make sure when i = 0 or 1, NavigationCompleted handler is called?
I reproduced your issue. The issue is caused by the for loop continue before the previous navigation completed.
The key point to resolve this is to wait for the previous NavigationCompleted event handle occurred.You could use AutoResetEvent which notifies a waiting thread that an event has occurred. For example:
public MainPage()
{
this.InitializeComponent();
urls = new List<Uri>();
waitForNavComplete = new AutoResetEvent(false);
urls.Add(...);
}
List<Uri> urls;
AutoResetEvent waitForNavComplete;
private async void btnnavigate_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 3; i++)
{
mywebview.Navigate(urls[i]);
await Task.Run(() => { waitForNavComplete.WaitOne(); });
}
}
private void mywebview_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
System.Diagnostics.Debug.WriteLine(args.Uri.ToString());
waitForNavComplete.Set();
}
Using Set method to set the state of the completed event to signaled so that for loop will stop waiting and continue.
I think #sunteen-wu-msft's solution is a bit complicated. Sloppy use of AutoResetEvent can lead to a lot of headache. If the goal is to let NavigationCompleted to fire, then why not just navigate to the next URL when first one is completed, e.g. call NavigateToLocalStreamUri in the NavigationCompleted itself:
private Stack<Uri> toNavigate = ...
private void mywebview_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
// Handle NavigationCompleted
...
// Navigate to the next Uri
var uri = toNavigate.Pop();
mywebview.NavigateToLocalStreamUri(uri, resolver);
}
Another option would be to simply create several webviews and navigate them all to different Uris. This can take quite a lot of resources though since all loaded websites will live in memory until you kill its webview.
I have a thread with WebBrowser instance and I have attached to it an DocumentCompleted event. But from what I have observed, my event isn't raised, because thread ends before it happens. When I put MessageBox.Show on the end of thread, it gives time for the event to be raised. But how can I make thread wait for it without MessageBox?
WebBrowser browser;
Thread t = new Thread(() =>
{
string url = string.Format("webpage.com");
browser = new WebBrowser();
browser.ScriptErrorsSuppressed = true;
browser.Navigate(url);
browser.DocumentCompleted += browser_DocumentCompleted;
browser.DocumentCompleted += (o, a) =>
{
//MessageBox.Show("In DocumentCompleted.");
List<Status> statusy = new List<Status>();
IHTMLDocument2 currentDoc = (IHTMLDocument2)browser.Document.DomDocument;
//parsing the html doc
string Statuses = "";
foreach (Status status in statusy)
{
Statuses += String.Format("{0} {1} - {2} --> {3}{4}", status.Date, status.Time, status.Centre, status.Message, Environment.NewLine);
}
MessageBox.Show(Statuses);
};
MessageBox.Show("In thread!!!!");
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
You must call Application.Run(). Not just to ensure your thread doesn't end too soon, it is also required to get WebBrowser to raise its events. Using the message loop is a standard way in which heavily threaded components, like WebBrowser, ensure that its events are raised on the same thread that created the object. And it implements the STA contract.
The message loop that MessageBox.Show() uses under the hood to make itself modal is why it works right now when you use MessageBox. Not otherwise fundamentally different from the message loop that Application.Run() implements.
Use Application.ExitThread() to get the thread to end. It must be called on the same thread that called Application.Run(). That won't be a problem when you do it in the DocumentCompleted event handler.
The main problem is that the web browser control requires a proper message loop to work properly. The thread you're launching has no such message loop, so the web browser can't really do much.
The easiest solution would be to simply host the browser control in a form - this gives you easy control over the lifetime of the browser, and an easy way to maintain the message loop (that's what Application.Run does).
If that's not applicable for you (that is, you don't want to show any form at all), you'll need to make a form-less message loop. The simplest example using your code:
WebBrowser browser;
Thread t = new Thread(() =>
{
string url = string.Format("google.com");
browser = new WebBrowser();
browser.ScriptErrorsSuppressed = true;
browser.Navigate(url);
browser.DocumentCompleted += (o, a) =>
{
MessageBox.Show(browser.Document.Title);
Application.ExitThread();
};
Application.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
If you need to wait on the event from some other thread, there's plenty of ways to synchronize. A simple way to pass data at the same time and with a nice Task-based interface is the TaskCompletionSource class. For example, if I want to await the title of the document asynchronously, it's as simple as this:
WebBrowser browser;
var tcs = new TaskCompletionSource<string>();
Thread t = new Thread(() =>
{
string url = string.Format("google.com");
browser = new WebBrowser();
browser.ScriptErrorsSuppressed = true;
browser.Navigate(url);
browser.DocumentCompleted += (o, a) =>
{
tcs.SetResult(browser.Document.Title);
Application.ExitThread();
};
Application.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
Console.WriteLine(await tcs.Task);
Of course, this assumes the callee is an async method, but there's plenty of other things you can do with the task - for example, register a continuation.
You don't need to keep a reference to the Thread instance - started threads are roots, so they will never be collected.
How about setting a flag in the DocumentCompleted event handler, and then waiting for the flag to be set where you are currently doing the MessageBox.Show()?
Use a flag. In the DocumentCompleted event handler, set the flag to false.
Then use a while statement like:
bool Flag = true;
browser.DocumentCompleted += (o, a) =>
{
//MessageBox.Show("In DocumentCompleted.");
List<Status> statusy = new List<Status>();
IHTMLDocument2 currentDoc = (IHTMLDocument2)browser.Document.DomDocument;
//parsing the html doc
string Statuses = "";
foreach (Status status in statusy)
{
Statuses += String.Format("{0} {1} - {2} --> {3}{4}", status.Date, status.Time, status.Centre, status.Message, Environment.NewLine);
}
MessageBox.Show(Statuses);
Flag = false;
};
while (Flag) { }
So here is the problem: I have a basic application which uses a thread to call and update a new form containing a textblock. But the new form that is called is infinitely loading (blank screen) when it's called in my thread.
Here is my code that instantiates and calls the thread after clicking on a button:
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread infoThread = new Thread(new ThreadStart(update));
infoThread.SetApartmentState(ApartmentState.STA);
infoThread.Start();
}
And here is the thread's function code:
private void update()
{
string response;
InfoWindow infoWindow = new InfoWindow();
infoWindow.Show();
while(Thread.CurrentThread.IsAlive)
{
response = sendRequest(1, 0, 3, 1, "test");
infoWindow.tb_infoBlock.Text += response + "\n";
Thread.Sleep(1000);
}
}
It basically creates a new infoWindow (The window that I actually want to update), and shows it. Then my thread calls a function that sends a request to a local server every X seconds and updates my textBlock with the response that is returned.
Note that I made my sendRequest function only return a 4 length string to try updating my block.
I really don't get the problem.. Any ideas?
The thread that creates the window and loops never gets the time to update the window. It either reads, sets the text or sleeps.
Put the reading, setting and sleeping in an other thread and instead of setting the property directly use BeginInvoke of the form to have the form's thread update the UI of the form.
In creating jpg images, this code uses threading. However, the Thread.Join() sometimes hangs on creating particular images. I have researched, and it seems as if I should be using BeginInvoke() instead. How could I rewrite the following code from using Thread.Join() to BeginInvoke()?
public Bitmap Generate()
{
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser {ScrollBarsEnabled = false };
browser.ScriptErrorsSuppressed = true;
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
Looking at your code I see one issue. You've registered to the DocumentCompleted event after the Navigate() call. So theoretically it's possible that the event have been fired before you've registered your handler.
Try to swap the two lines and see whether you get your problem fixed.
I believe that'll be the case if the image has already been retrieved and was cached.
I got to load some data out of a db4o database which takes 1 or 2 seconds at the startup of my app, the rest has to wait because first of all the data has to be loaded. doing this in an own thread would mean that the rest has to wait for the thread-finishing. I'd like to do a splash screen or something during the data is loaded for what also need an own thread, right? how would you do?
I'm using csharp, .net 3.5 and winforms
Showing a splash screen at startup is easy to do. In your application's Main() method (in Program.cs), put something like this before the Application.Run(...) line:
SplashForm splashy = new SplashForm();
splashy.Show();
Application.Run(new MainForm(splashy));
Modify the code and constructor for your main form so that it looks something like this:
private SplashForm _splashy;
public MainForm(SplashForm splashy)
{
_splashy = splashy;
InitializeComponent();
}
Then at the end of your MainForm's Load event (which presumably contains the database code), put this code:
_splashy.Close();
_splashy.Dispose();
If you choose to do your database access with a separate Thread or BackgroundWorker, then you don't really need a splash screen so much as you need some sort of progress indicator form that appears while the BackgroundWorker is doing its thing. That would be done differently from my answer here.
One way, probably better ways though. Create a new dialog form that will be your progress window/splash screen. Throw a bitmap or whatever on it as the only item. Instantiate the dialog from your main program. Override the Load event for the progress form and from there launch the new thread that will do the background processing work for loading up the data. This way you can just call ShowDialog from your main app.
if you use System.ComponentModel.BackgroundWorker then you can easily wire up events for when the thread completes and automaticaly exit the dialog from that event. Control is returned back to the calling application and you're done.
I've done this sort of thing in an application before and it works fine but I'm sure it's a novice approach. Here's sample code from the Load event in the form that launches the background thread (in my case I'm opening and parsing large files):
private void FileThreadStatusDialog_Load(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
if (m_OpenMode)
{
this.Text = "Opening...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileOpenThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
else
{
this.Text = "Saving...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileSaveThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
}
And here's what the work completed method looks like which exist the form:
private void FileThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
FileThread = null;
DialogResult = DialogResult.OK;
Close();
}
Here's how I open up the progress dialog from the main dialog:
FileThreadStatusDialog thread = new FileThreadStatusDialog(m_Engine, dlg.FileName, true);
if (thread.ShowDialog(this) == DialogResult.OK)
{
m_Engine = thread.Engine;
FillTree();
}
One might want to force drawing of splashy in MusiGenesis' answer by adding
Application.DoEvents();
immediately after
splashy.Show();