I am trying to create a multi-threaded application that creates WebBrowsers and does specific things to each one. When I tried my code from the main thread it worked great, However, When I changed the code to run from a thread, the code runs fine until InvokeMember("click") is called and nothing happens. InvokeMember() isn't executed and the button click doesn't take place. Here is my code:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(Work);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
[STAThread]
void Work()
{
WebBrowser wb = new WebBrowser();
wb.ScriptErrorsSuppressed = false;
wb.Visible = true;
wb.Navigate("http://website.com");
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
//updateText("Loaded");
wb.Document.GetElementById("F1").SetAttribute("Value", "Test");
wb.Document.GetElementById("F2").SetAttribute("Value", "Saracostaz");
wb.Document.GetElementById("F3").SetAttribute("Value", "Tester5123#hotmail.com");
wb.Document.GetElementById("F4").SetAttribute("Value", "Tester5123#hotmail.com");
wb.Document.GetElementById("F5").SetAttribute("Value", "limewire");
wb.Document.GetElementById("F6").SetAttribute("SelectedIndex", "1");
wb.Document.GetElementById("F7").SetAttribute("SelectedIndex", "2");
wb.Document.GetElementById("F8").SetAttribute("SelectedIndex", "5");
wb.Document.GetElementById("F9").SetAttribute("SelectedIndex", "20");
// updateText("Entered Data");
HtmlElementCollection elements = wb.Document.Body.All;
foreach (HtmlElement element in elements)
{
string valueAttribute = element.GetAttribute("value");
if (!string.IsNullOrEmpty(valueAttribute) && valueAttribute == "Sign Up")
{
element.InvokeMember("click");
//MessageBox.show("I am in"); //that messagebox shows normally.
break;
}
}
}
Please note that the Work() runs very correctly when it's called from the main thread. The problem lies in calling it from another thread.
Thanks in advance.
You are violating a hard requirement for an STA thread, it must pump a message loop. You patched your way out of trouble with the Navigate method by calling Application.DoEvents(). That pumps the message loop. But you are not doing this for InvokeClick.
Check this answer for the solution. Put the code in the DocumentCompleted event. There is no obvious way I see to decide when to stop the thread, you'd have to poll for some kind of side-effect of the click with a timer perhaps.
JFYI, you can do next using LINQ:
var element = elements
.OfType<HtmlElement>()
.Select(element => element.GetAttribute("value"))
.FirstOrDefault(value=> String.Equals(value, "Sign Up"));
if (element != null)
element.InvokeMember("click");
Related
My program works like this:
I press a radio button which opens the port.
Next i press a button "Read" which starts a thread that reads data continously from the Serial Port using port.ReadLine() and prints it in a textbox;
I have another radio which should first join the thread and after that close the port;the problem is the printing goes well until i close the port when the UI freezes.
public Form1()
{
mythread = new Thread(ReadFct);
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
Below is the function attached to the thread
void ReadFct()
{
string aux = "";
while (readCondition)
{
if (myPort.IsOpen)
aux = myPort.ReadLine();
this.SetText(aux);
}
}
Below is the radio button event handler
public void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
try
{
myPort.Open();
mythread.Start();
}
catch (Exception)
{
MessageBox.Show("Nu s-a putut deschide port-ul");
}
if (radioClose.Checked && myPort.IsOpen)
{
readCondition = false;
mythread.Join();
myPort.Close();
// myPort.DataReceived -= DataReceivedHandler;
}
}
The read button function:
private void readbtn_Click(object sender, EventArgs e)
{
if (!myPort.IsOpen)
MessageBox.Show("PORT NOT OPENED!");
else
{
// myPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
readCondition = true;
if (!mythread.IsAlive)
{
mythread = new Thread(ReadFct);
mythread.Start();
}
}
I have used what MSDN suggest when changing control from another thread:
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
StringTb del = new StringTb(SetText);
this.Invoke(del, new object[] { text });
}
else
SetData = text;
}
It's hard to know exactly what you need, lacking a good Minimal, Complete, and Verifiable code example to illustrate the question. That said, the issue here is that the Thread.Join() method causes that thread to stop doing any other work, and the thread you use to call that method is the thread that handles all of the user interface. Worse, if your port never receives another newline, the thread you're waiting on will never terminate, because you're stuck waiting on the ReadLine() method. Even worse, even if you do get a newline, if that happens while you're stuck waiting on the Thread.Join(), the call to Invoke() will deadlock, because it needs the UI thread to do its work, and the Thread.Join() call is preventing it from getting the UI thread.
In other words, your code has multiple problems, any one of which could cause problems, but all of which together mean it just can't possibly work.
There are a variety of strategies to fix this, but IMHO the best is to use await. The first step in doing that is to change your I/O handling so that it's done asynchronously instead of dedicating a thread to it:
// Ideally, you should rename this method to "ReadFctAsync". I am leaving
// all names intact for the same of the example though.
async Task ReadFct()
{
string aux = "";
using (StreamReader reader = new StreamReader(myPort.BaseStream))
{
while (true)
{
aux = await reader.ReadLineAsync();
// This will automatically work, because the "await" will automatically
// resume the method execution in the UI thread where you need it.
this.SetText(aux);
}
}
}
Then, instead of creating a thread explicitly, just create a Task object by calling the above:
public Form1()
{
// In this approach, you can get rid of the "mythread" field altogether
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
public async void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
{
try
{
myPort.Open();
await ReadFct();
// Execution of this method will resume after the ReadFct() task
// has completed. Which it will do only on throwing an exception.
// This code doesn't have any continuation after the "await", except
// to handle that exception.
}
catch (Exception)
{
// This block will catch the exception thrown when the port is
// closed. NOTE: you should not catch "Exception". Figure out what
// *specific* exceptions you expect to happen and which you can
// handle gracefully. Any other exception can mean big trouble,
// and doing anything other than logging and terminating the process
// can lead to data corruption or other undesirable behavior from
// the program.
MessageBox.Show("Nu s-a putut deschide port-ul");
}
// Return here. We don't want the rest of the code executing after the
// continuation, because the radio button state might have changed
// by then, and we really only want this call to do work for the button
// that was selected when the method was first called. Note that it
// is probably even better if you just break this into two different
// event handlers, one for each button that might be checked.
return;
}
if (radioClose.Checked && myPort.IsOpen)
{
// Closing the port should cause `ReadLineAsync()` to throw an
// exception, which will terminate the read loop and the ReadFct()
// task
myPort.Close();
}
}
In the above, I have completely ignored the readbtn_Click() method. Lacking a good MCVE, it's not clear what role that button plays in the overall scheme. You seem to have a radio button group (of two buttons) that control whether the port is open or closed. It is not clear why then you have an additional regular button that is seemingly able to also open the port and start reading, independently of the radio group.
If you want that extra button, it seems to me that all it ought to do is change the radio group state, by checking the "open" radio button. Then let the radio group buttons handle the port state and reading. If you need more specific advice as to how to fully integrate my code example above with your entire UI, you will need to provide more detail, preferably in a new question. That new question must include a good MCVE.
First, I want to check if my object is null. It will sleep 20 milliseconds in while loop gives user can be interactive with UI.
If the condition in while is correct, I can click the button in UI to break this loop and continues other code.
I tried with by original code:
while (browser.FindElementById("iframe") == null)
{
if(buttonWasClicked == true)
return;
Thread.Sleep(20);
}
I was tried with:
Task.Delay(10000).ContinueWith(x =>
{
while (browser.FindElementById("iframe") == null)
{
if(buttonWasClicked == true)
return;
}
});
Seem block code working in the background. So, it skips block code and executes all next line code.
I want it must check before executing next code.
So, I tried with another method. I using Timer but I don't usually use Timer in the application, I don't have any experience with this.
I created new Timer like timer1 and set interval = 20.
At timer1_Tick event, I add code:
private void timer1_Tick(object sender, EventArgs e)
{
if (browser.FindElementById("iframe") == null)
{
if(buttonWasClicked == true)
break;
}
else
timer1.Stop();
}
And at original code, I replace with:
timer1.Start();
At button_Clicked event, I set to:
timer1.Stop();.
Have any issues with my code?
In debug, it skip event timer1_Tick() and execute all code after line timer.Start().
Assuming your "wait for button push" code is in the GUI thread, this looks like an good use for await. Consider creating an awaitable event (manual or auto) and using that instead of buttonWasClicked. You main code would then look like:
evtButtonClicked = new AsyncManualResetEvent();
await evtButtonClicked.WaitAsync();
// continuation code...
Now, when you call evtButtonClicked.Set(), the continuation code will be queued up to execute on the GUI thread. Recall that the code after await is effectively wrapped into a Task.ContinueWith(), which means that your GUI thread will not block while waiting for the event.
I'm writing an application that will automate one of our manual webform input processes
Everything is working good except for one problem.
I have a Timer setup, that becomes enabled on a certain page. The Timer tick event is checking the page every 100 milliseconds for ajax changes applied to the page. Once the ajax updates are detected, the Timer is disabled, the result is stored, and the program SHOULD continue executing code beyond that point.
The problem is the code continues to execute while the Timer is enabled.
In the logic, as soon as the appropriate page loads, I have
t2.Enabled = true;
Which immediately works as it should, looking at the page until the update is discovered
But the code immediately following the Enabled property is executing without pause, causing many issues, such as variables changing before the result is discovered.
How can I have the code following this line wait until the t2.Enabled is set back to false (which is done within the t2_Tick(object sender, EventArgs e) method
void t2_Tick(object sender, EventArgs e)
{
string postVerifyHTML = string.Empty;
try
{
postVerifyHTML = wb.Document.Body.InnerHtml;
}
// if page fails, restart
catch
{
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
if (postVerifyHTML.IndexOf("indentifier html") != -1)
{
NameSearchResults[nameCounter].Visited = true;
nameCounter++;
ResultFound = true;
t2.Enabled = false;
}
t2TimerCount++;
if (t2TimerCount >= 100)
{
// TRY AGAIN
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
}
protected void wb_SearchForm_DocumentCompleted(object sender, EventArgs e)
{
string pageHTML = wb.Document.Body.InnerHtml;
// Look at the page with the name result
if (pageHTML.IndexOf("Search Results: Verify") != -1)
{
//If the page has this input, a verification is available
if (pageHTML.IndexOf("txtSSN") != -1)
{
HtmlElement txtSSN = wb.Document.GetElementById("txtSSN");
txtSSN.SetAttribute("value", curSearchRecord.UniqueId.Replace("-", "").Replace(" ", ""));
HtmlElement submitBtn = wb.Document.GetElementById("ibtnVerify");
submitBtn.InvokeMember("click");
t2.Enabled = true;
// I need the code after this point to wait until the Timer is disabled
}
The Timer is running on a different thread to your UI code, which is why your execution is continuing. Why don't you simply check the Enabled state of the Timer to determine whether or not to continue the execution? Alternatively use the callback of your ajax code to fire off the continuation code.
Im not sure this is the best method to to it but you can do a do an if like so :
if (t2.Enabled=False)
{
//the code you want to run when the timer is off
}
but you have to make sure that it is in another timer (t3 in this case if you want) otherwise it wont check every tick if t2 is off to run the code while it is.
sorry if the answer is not more detailed, I lacked details in your question as well.
Good programing :)
You could try to use a ManualResetEvent as a member of your class
After you enable the Timer, you call the WaitOne method
After your disable the Timer, you call the Set method
private ManualResetevent mre = new ManualResetEvent(false);
void t2_Tick(object sender, EventArgs e)
{
string postVerifyHTML = string.Empty;
try
{
postVerifyHTML = wb.Document.Body.InnerHtml;
}
// if page fails, restart
catch
{
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
if (postVerifyHTML.IndexOf("indentifier html") != -1)
{
NameSearchResults[nameCounter].Visited = true;
nameCounter++;
ResultFound = true;
t2.Enabled = false;
//Set the mre to unblock the blocked code
mre.Set();
}
t2TimerCount++;
if (t2TimerCount >= 100)
{
// TRY AGAIN
wb.Navigate(new Uri("http://www.website.com"), "_self");
}
}
protected void wb_SearchForm_DocumentCompleted(object sender, EventArgs e)
{
string pageHTML = wb.Document.Body.InnerHtml;
// Look at the page with the name result
if (pageHTML.IndexOf("Search Results: Verify") != -1)
{
//If the page has this input, a verification is available
if (pageHTML.IndexOf("txtSSN") != -1)
{
HtmlElement txtSSN = wb.Document.GetElementById("txtSSN");
txtSSN.SetAttribute("value", curSearchRecord.UniqueId.Replace("-", "").Replace(" ", ""));
HtmlElement submitBtn = wb.Document.GetElementById("ibtnVerify");
submitBtn.InvokeMember("click");
t2.Enabled = true;
//The code will block until Set() is called on mre
mre.WaitOne();
//The rest of your code here
}
I am using the below code to start and stop the foreach loop execution. But start is working fine. I can't stop the loop execution while stop button click. Please help me to do this.
private void btn_start_Click(object sender, EventArgs e)
{
if (txt_rows.Text != "") {
thread = new System.Threading.Thread(new System.Threading.ThreadStart(update));
thread.IsBackground = true;
thread.Start();
}
}
private void Update()
{
//My logic here
}
private void btn_stop_Click(object sender, EventArgs e)
{
lbl_appstatus.Text = "Stop"; // Button click event not fired while run the loop
lbl_appstatus.Update();
lbl_appstatus.ForeColor = System.Drawing.Color.Red;
if (thread != null && thread.IsAlive)
thread.Abort();
}
In your code, if you click twice or more on start button you loos your previous thread objects; Thus you should not create more than one thread.
The "Start" and "Stop" clicks are separate requests to that codebehind. These use separate instances of you Page class, which usually means two separate "thread" variables (you didn't show the declaration).
So on the "Stop" click that thread value is always null.
For an "InProc" session, maybe it's possible to store that thread value in Session, so you can keep the value across requests.
I'm new to using event handlers and backgroundworkers, so I may be missing something completely obvious here. Still, I've been trying to fix this for two days, so I thought I might as well see what anyone had to say.
I have a backgroundworker called SqlExpressDownloader. It starts running at the beginning of my program, the rest of the work runs, and then it should wait for the operations in the SqlExpressDownloader_DoWork() method to complete before continuing. The only problem is that for some reason whenever I do while(SqlExpressDownloader.IsBusy), it always responds as busy and therefore will wait forever.
The code for the event handler is here:
private void SqlExpressDownloader_DoWork(object sender, DoWorkEventArgs e)
{
string sSource = string.Format("{0}\\{1}", Paths.Settings_Common, "sqlexpr_x64_enu.exe");
Debug.WriteLine(sSource);
Debug.WriteLine("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe");
if (!System.IO.File.Exists(sSource))
{
WebClient oWebClient = new WebClient();
oWebClient.DownloadProgressChanged += DownloadProgressChanged;
oWebClient.DownloadDataCompleted += DownloadComplete;
oWebClient.DownloadFileAsync(new System.Uri("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe"), sSource);
while (oWebClient.IsBusy)
{
Thread.Sleep(100);
}
e.Result = "";
DownloadFinished = true;
}
}
I have watched the code and have watched it complete this method. I even added a return after the DownloadFinished = true, but it still responds as busy. What I want to know is how to make the backgroundworker respond as not busy.
EDIT
The events are all added in the constructor as shown here:
SqlExpressDownloader = new BackgroundWorker();
SqlExpressDownloader.DoWork += new DoWorkEventHandler(this.SqlExpressDownloader_DoWork);
SqlExpressDownloader.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.SqlExpressDownloader_RunWorkerCompleted);
The RunWorkerCompleteEventHandler looks like this:
private void SqlExpressDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
Debug.WriteLine("The actions are complete.");
}
else
{
Debug.WriteLine("Error in completed work.");
}
}
But, when I debugged it last, it didn't actually trigger.
Instead of querying SqlExpressDownloader.IsBusy in a loop, try subscribing to the RunWorkerCompleted event of the BackgroundWorker and place your code in there that should only occur after the DoWork event has completed.
You'll also have access to the RunWorkerCompletedEventArgs, which you can check to make sure no error was thrown from the DoWork portion of your BackgroundWorker.
...
...
SqlExpressDownloader.RunWorkerCompleted += SqlExpressDownloader_RunWorkerCompleted;
SqlExpressDownloader.RunWorkerAsync();
}
private void SqlExpressDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// do something in response to the error
}
// stuff to do after DoWork has completed
}
I found Joe Albahari's tutorial helpful when I was learning how to use these.
You can replace your code with more elegant async/await solution like this
private async Task SqlExpressDownloadAsync()
{
string sSource = string.Format("{0}\\{1}", Paths.Settings_Common, "sqlexpr_x64_enu.exe");
Debug.WriteLine(sSource);
Debug.WriteLine("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe");
if (!System.IO.File.Exists(sSource))
{
WebClient oWebClient = new WebClient();
oWebClient.DownloadProgressChanged += DownloadProgressChanged;
oWebClient.DownloadDataCompleted += DownloadComplete;
await oWebClient.DownloadFileTaskAsync(new System.Uri("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe"), sSource);
}
}
I had a similar issue. DownloadASync would fire but .IsBusy would always stay on true.
This probably won't be a common problem, just thought I share my resolution.
I used
MessageBox.Show(new Form() { TopMost = true }, "", "")
This was the cause. I also tried:
var t = new Form() { TopMost = true };
MessageBox.Show(t, "", "");
t.Dispose();
This caused the same issue.
My code had multiple threads, I assume one of them must have gotten stuck, or perhaps the MessageBox(the new Form() { TopMost = true; } ) call created a stuck thread.
As soon as I removed that part, eg.
MessageBox.Show("", "");
Everything worked as expected again.
So maybe you are creating another thread somewhere that is causing your issue.