Im building a program that surf to several websites and do something.
After surfing to like 5 urls successfully, the program hangs after the Application.Run() line.
The program doesn't even enter the Handler function and just stuck. the CPU usage is 0 at this point.
I tried closing the threads in any possible way.
What i'm doing wrong?
I'm doing it like that:
[STAThread]
private static void Main(string[] args)
{
for (int i = 0; i < urls.Count; i++)
{
var th = new Thread(() = >
{
var weBrowser = new WebBrowser();
weBrowser.AllowNavigation = true;
weBrowser.DocumentCompleted += Handler;
weBrowser.Navigate(urls[i]);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
}
}
And my Handle function is:
private static void Handler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser weBrowser = sender as WebBrowser;
var htmlDocument = weBrowser.Document;
/*do something*/
Application.Exit();
Application.ExitThread();
weBrowser.Dispose();
weBrowser.Stop();
Thread.CurrentThread.Abort();
}
My problem is very similar to this one:
Application.Run() leads to application hanging
There is no answer in this question either.
Thanks!
I think you are doing several mistakes:
you are joining inside the for look
you are calling Application.Exit() in each handler call
You should move the joining outside the for loop and do not call Application.Exit.
The following sample seems to work well:
static class Program
{
[STAThread]
static void Main()
{
var urls = new List<string>() {
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com",
"http://stackoverflow.com"};
var threads = new Thread[urls.Count];
for (int i = 0; i < urls.Count; i++)
{
threads[i] = new Thread((url) =>
{
var weBrowser = new WebBrowser();
weBrowser.AllowNavigation = true;
weBrowser.DocumentCompleted += Handler;
weBrowser.Navigate(url as string);
Application.Run();
});
threads[i].SetApartmentState(ApartmentState.STA);
threads[i].Start(urls[i]);
}
foreach (var t in threads)
t.Join();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
private static void Handler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser weBrowser = sender as WebBrowser;
var htmlDocument = weBrowser.Document;
/*do something*/
Application.ExitThread();
weBrowser.Dispose();
weBrowser.Stop();
}
}
You may be running into the maximum number of concurrent connections for the WebBrowser. By explicitly setting this to a higher number, you can have additional streams reading through the browser at once.
// Example Usage:
ServicePointManager.DefaultConnectionLimit = 10;
Keep in mind that there is a performance hit by increasing this number above the default (I think it is 4) as you will have much more network traffic that needs processed.
See the MSDN article for ConnectionLimit for more information.
I don't understand what would you like to achieve with Application.Run inside for loop.
Why are you using WebBrowser component? If you are just parsing web page it is better to use
string urlAddress = "http://stackoverflow.com";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlAddress);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
StreamReader reader= null;
if (response.CharacterSet == null)
reader = new StreamReader(response.GetResponseStream());
else
reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(response.CharacterSet));
string data = reader.ReadToEnd();
response.Close();
reader.Close();
}
or
using (WebClient client = new WebClient())
{
string html = client.DownloadString("http://stackoverflow.com");
}
For parsing html look at Html Agility Pack or something similar.
If this is console application you don't need to call Application.Run(), otherwise you should consider showing splash screen with progress to give user some feedback.
Usage of urls[i] in your original snippets is wrong. Search C# documentation for closures. You will need to make a local copy before using it.
Furthermore, you should swap weBrowser.Dispose() and weBrowser.Stop(). You can't stop the disposed browser anymore (if Stop is necessary at all).
Finally, don't abort the thread - it will finish itself.
Related
I am using the following function to get response of a url from web. when i place the response in message box it displays properly.when i put it in text box the programme gets stuck on that statement and doesn't move.why is it happening,as i am feeding the string(response_message) to textbox.
void message_send(int j)
{
int y = 0;
if (CONTACT_NO[j] != "")
{
string Message = "hello";
string url = "some url not mentioned here ";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Timeout = 30000;
using (WebResponse response = (HttpWebResponse)request.GetResponse())
{
byte[] bytes = ReadFully(response.GetResponseStream());
response_message = System.Text.Encoding.UTF8.GetString(bytes);
// error_logs(str);
MessageBox.Show(response_message);
textBox2.Text = response_message;
}
//textBox3.Text = response_message;
}
else
{
messagebox.show("some message");
}
Since you're multithreading your update to your textbox, you need to be aware that your worker thread can't update the textbox because the textbox wasn't created on the worker thread.
To update the textbox in a thread safe way, I'd recommend the easiest way of invoking:
TextBox2.Invoke((MethodInvoker)(() => { TextBox2.Text = response_message });
I was using my this function in backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) function.which i guess is separate thread.due to which i wasn't able to access textbox1.hence i used the following method to access it.
private void SetText1(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText1);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox2.Text = text;
}
}
Mistake that i was doing i wasn't using the try catch method to check for the exception.which helps me in understanding my issue.
I'm trying to get my head around threads, in my current application threads would be the best way to deal with it, what i have:
public void threadsTest() {
Invoke(new MethodInvoker(() => {
// loop over the listview getting the first value
foreach (ListViewItem item in listViewBacklinks.Items)
{
// try...
try
{
var mainURL = item.Text;
using (WebClient wc = new WebClient())
{
try
{
var pageHtml = wc.DownloadString(mainURL);
if (pageHtml.Contains(Text))
{
var subitems = item.SubItems.Add("anchor text");
item.BackColor = Color.Green;
}
else
{
item.BackColor = Color.Red;
}
} catch (Exception)
{
item.BackColor = Color.Red;
}
//Helpers.returnMessage("Work done!");
}
} catch (Exception ex) {
Helpers.returnMessage(ex.ToString());
}
}}));
}
private void btnAnalyzeAnchorText_Click(object sender, EventArgs e)
{
// attempting threads
var t = new Thread(threadsTest);
t.Start();
}
I thought like the backgroundWorker it would not freeze up the GUI but it did so i put in the invoke to access GUI elements, i don't think it looks right, the way it is now the GUI is still unresponsive until the work is done, i have viewed some tutorials but it's not totally sinking in, any help/tips on cracking threads would be great.
This is what you need to do:
public void threadsTest(string[] urls)
{
var results = new Dictionary<string, string>();
foreach (string mainURL in urls)
{
using (WebClient wc = new WebClient())
{
var pageHtml = wc.DownloadString(mainURL);
results[mainUrl] = pageHtml;
}
}
Invoke(new MethodInvoker(() => ProcessResults(results)));
}
There are no UI elements in this code, just the heavy lifting of the WebClient.
Then in the new ProcessResults you're back on the UI thread so then you can set your results.
The call to the thread needs to pass through an array of urls (strings) but you get that before creating the thread so you're still in the UI thread and thus it is safe to do.
This is the code I use to record an audio file:
internal class AudioRecorder
{
public WaveIn waveSource = null;
public WaveFileWriter waveFile = null;
public string RECORDING_PATH;
public AudioRecorder(string fileName)
{
RECORDING_PATH = fileName;
}
public void Start()
{
waveSource = new WaveIn();
waveSource.WaveFormat = new WaveFormat(44100, 1);
waveSource.DeviceNumber = 0;
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
waveFile = new WaveFileWriter(RECORDING_PATH, waveSource.WaveFormat);
System.Timers.Timer t = new System.Timers.Timer(30000);
t.Elapsed += new ElapsedEventHandler(Stop);
waveSource.StartRecording();
t.Start();
}
private void Stop(object sender, ElapsedEventArgs args)
{
waveSource.StopRecording();
}
private void waveSource_DataAvailable(object sender, WaveInEventArgs e)
{
if (waveFile != null)
{
waveFile.Write(e.Buffer, 0, e.BytesRecorded);
waveFile.Flush();
}
}
private void waveSource_RecordingStopped(object sender, StoppedEventArgs e)
{
if (waveSource != null)
{
waveSource.Dispose();
waveSource = null;
}
if (waveFile != null)
{
waveFile.Dispose();
waveFile = null;
}
}
}
In the main method I do:
AudioRecorder r = new AudioRecorder(dialog.FileName);
r.Start();
FileInfo file = new FileInfo(r.RECORDING_PATH);
// Do somehting with the recorded audio //
The problem is that when I do r.Start() the thread does not block and keeps running. So I get a corrupt file error. When I try things like Thread.Sleep to keep the thread waiting until recording finishes, this time the AudioRecorder code does not work well (i.e. recording never finishes).
Any ideas about what should I do to correctly wait the recording to finish so that I can safely use the recorded file ?
If you want to record for 30 seconds exactly, just call StopRecording in the DataAvailable event handler once you have enough data. There is absolutely no need for a complicated threading strategy. I do exactly this in the open source .NET voice recorder application.
Dispose the WaveFileWriter in the RecordingStopped event.
If you absolutely must have a blocking call, then use WaveInEvent, and wait on an event which is set in the RecordingStopped handler, as suggested by Rene. By using WaveInEvent, you remove the need for windows message pump to be operational.
You use a ManualResetEvent to wait for the Stop event to be called, giving other threads a change to proceed.
I've only added the new bits...
internal class AudioRecorder
{
private ManualResetEvent mre = new ManualResetEvent(false);
public void Start()
{
t.Start();
while (!mre.WaitOne(200))
{
// NAudio requires the windows message pump to be operational
// this works but you better raise an event
Application.DoEvents();
}
}
private void Stop(object sender, ElapsedEventArgs args)
{
// better: raise an event from here!
waveSource.StopRecording();
}
private void waveSource_RecordingStopped(object sender, EventArgs e)
{
/// ... your code here
mre.Set(); // signal thread we're done!
}
It is good idea to avoid any multi-threaded code if it is not required and Mark's answer is explaining this perfectly.
However, if you are writing a windows application and the requirement is to record 30 seconds than it is a must not to block a main thread in waiting (for 30 seconds). The new async C# feature can be very handy here. It will allow you to keep code logic straightforward and implement waiting in a very efficient way.
I have modified your code slightly to show how the async feature can be used in this case.
Here is the Record method:
public async Task RecordFixedTime(TimeSpan span)
{
waveSource = new WaveIn {WaveFormat = new WaveFormat(44100, 1), DeviceNumber = 0};
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
waveFile = new WaveFileWriter(RECORDING_PATH, waveSource.WaveFormat);
waveSource.StartRecording();
await Task.Delay(span);
waveSource.StopRecording();
}
Example of using Record from click handler of WPF app:
private async void btnRecord_Click(object sender, RoutedEventArgs e)
{
try
{
btnRecord.IsEnabled = false;
var fileName = Path.GetTempFileName() + ".wav";
var recorder = new AudioRecorder(fileName);
await recorder.RecordFixedTime(TimeSpan.FromSeconds(5));
Process.Start(fileName);
}
finally
{
btnRecord.IsEnabled = true;
}
}
However, you have to watch out for timing here. Task.Delay does not guarantee that it will continue execution after the exact specified time span. You might get records slightly longer than is required.
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);
}
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
}
}
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();
}
}
}
}
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:
how-to-use-httpwebrequest-net-asynchronously
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);
I am using webrequest to fetch some image data. The url may be invaild sometime. In case of invalid URL, begingetresponse is taking time equals to timeout period. Also the control become unresponsive during that period. In other word the async callback is not working asynchronously. Is this expected behaviour?
try
{
// Async requests
WebRequest request = WebRequest.Create(uri);
request.Timeout = RequestTimeOut;
RequestObject requestObject = new RequestObject();
requestObject.Request = request;
request.BeginGetResponse(this.ProcessImage, requestObject);
}
catch (Exception)
{
ShowErrorMessage(uri);
}
private void ProcessImage(IAsyncResult asyncResult)
{
try
{
RequestObject requestObject = (RequestObject)asyncResult.AsyncState;
WebRequest request = requestObject.Request;
WebResponse response = request.EndGetResponse(asyncResult);
Bitmap tile = new Bitmap(response.GetResponseStream());
// do something
}
catch (Exception)
{
ShowErrorMessage();
}
}
looks like this is an issue with .NET. BeginGetResponse blocks until DNS is resolved. In case of wrong URL (like http://somecrap) it tries until it gets timeout. See the following links -
link1 and link2
I just ran into this same situation. While it's not a perfect workaround I decided to use the Ping.SendAsync() to ping the site first. Good part is the async part return immediately. Bad part is the extra step AND not all sites respond to Ping requests.
public void Start(WatchArgs args)
{
var p = new System.Net.NetworkInformation.Ping();
args.State = p;
var po = new System.Net.NetworkInformation.PingOptions(10, true);
p.PingCompleted += new PingCompletedEventHandler(PingResponseReceived);
p.SendAsync(args.Machine.Name, 5 * 1000, Encoding.ASCII.GetBytes("watchdog"), po, args);
}
private void PingResponseReceived(object sender, .PingCompletedEventArgs e)
{
WatchArgs args = e.UserState as WatchArgs;
var p = args.State as System.Net.NetworkInformation.Ping;
p.PingCompleted -= new System.Net.NetworkInformation.PingCompletedEventHandler(HttpSmokeWatcher.PingResponseReceived);
args.State = null;
if (System.Net.NetworkInformation.IPStatus.Success == e.Reply.Status)
{
// ... BeginGetResponse now
}
else
{
/// ... machine not available
}
}
Just code and running for a day but initial result look promising.