I've got a Problem with the automatisation of WebBrowsing in an C#-Program. I've used the code before for a BHO and there it was working. But within a pure c# Program there seems to be some kind of deadlock. I've instructed my program to click on a search button and then wait (via a manualresetevent) for documentcomplete to signal. But now it seems that the click on the search button isn't processed until the ManualResetEvent signals a timeout. Thereafter the click is made and also the DocumentComplete-Event is fired.
For the structure of that programm: The WebBrowser-Control is part of a WindowsForm. The WebBrowser Control is passed to a Class which runs a Thread. This Class again passed the control to another class where the concrete behaviour according to the loaded webbrowser is programmed.
So in Code this lookes this way:
Setup of the thread
_runner = new Thread(runner); _runner.SetApartmentState(ApartmentState.STA); _runner.IsBackground = true; _runner.Start();
Processing of withingthe Runner-Method
b.placeTipp(workStructure);
The PlaceTipp-Method
public void placeTipp(ref OverallTippStructure tipp) { _expectedUrl = String.Empty; _betUrl = String.Empty; _status = BookieStatusType.CHECKLOGIN; while (true) { _mreWaitForAction.Reset(); checkIETab(); switch (_status) { case BookieStatusType.REQUESTWEBSITE: ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Keine IE-Tab vorhanden. Fordere eine an", this.BookieName)); //if (RequestIETabEvent != null) // RequestIETabEvent(this, new EventArgs()); _status = BookieStatusType.NAVIGATETOWEBSITE; _mreWaitForAction.Set(); break; case BookieStatusType.NAVIGATETOWEBSITE: _webBrowser.Navigate(@"http://www.nordicbet.com"); break; case BookieStatusType.CHECKLOGIN: checkLogin(); break; case BookieStatusType.LOGINNEEDED: doLogin(); break; case BookieStatusType.LOGGEDIN: _status = BookieStatusType.SEARCHTIPP; _mreWaitForAction.Set(); break; case BookieStatusType.SEARCHTIPP: searchTipp(tipp); break; case BookieStatusType.NAVTOSB: NavToSB(); break; case BookieStatusType.GETMARKET: getMarket(tipp); break; case BookieStatusType.PLACEBET: placeBet(tipp); break; case BookieStatusType.CONFIRMBET: confirmBet(); break; case BookieStatusType.EXTRACTBETDATA: extractBetId(ref tipp); break; case BookieStatusType.FINISHED: return; } if (!_mreWaitForAction.WaitOne(60000)) { //Sonderüberpüfung be LoginNeeded if (_status == BookieStatusType.LOGINNEEDED) { //checkLogin(); throw new BookieLoginFailedExcpetion(); } //TIMEOUT! ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Timeout bei warten auf nächsten Schritt. Status war {1}", this.BookieName, this._status.ToString())); throw new BookieTimeoutExcpetion(String.Format("Bookie {0}: Timeout bei dem Warten auf Ereignis", this.BookieName)); } } }
The SearchTipp-Method where the Deadlock is happening:
private void searchTipp(OverallTippStructure tipp) { if (_webBrowser.InvokeRequired) { _webBrowser.Invoke(new delegatePlaceBet(searchTipp), new object[] { tipp }); } else { ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Suche Tipp {1}", this.BookieName, tipp.BookieMatchName)); _expectedUrl = String.Empty; if (!_webBrowser.Url.ToString().StartsWith("https://www.nordicbet.com/eng/sportsbook")) { ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Nicht auf Sportsbookseite. Navigiere", this.BookieName)); _status = BookieStatusType.NAVTOSB; _mreWaitForAction.Set(); return; } _searchCompleted = false; HtmlDocument doc = _webBrowser.Document; if (doc != null) { mshtml.IHTMLInputElement elemSearch = (mshtml.IHTMLInputElement)doc.GetElementById("query").DomElement; if (elemSearch != null) { Thread.Sleep(Delayer.delay(2000, 10000)); elemSearch.value = tipp.BookieMatchName; mshtml.IHTMLElement elemSearchButton = (mshtml.IHTMLElement)doc.GetElementById("search_button").DomElement; if (elemSearchButton != null) { Thread.Sleep(Delayer.delay(900, 4000)); elemSearchButton.click(); //elemSearchButton.InvokeMember("click"); if (!_mreWaitForAction.WaitOne(10000)) //Here The Deadlock happens { //Now the click event and therefor the search will be executed ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Suche ist auf Timeout gelaufen", this.BookieName)); throw new BookieTimeoutExcpetion(String.Format("Bookie {0}: Suche ist auf Timeout gelaufen", this.BookieName)); } _mreWaitForAction.Reset(); HtmlElement spanResult = doc.GetElementById("total_ocs_count"); while (spanResult == null) { Thread.Sleep(500); spanResult = doc.GetElementById("total_ocs_count"); } int total_ocs_count = 0; if (!Int32.TryParse(spanResult.InnerHtml, out total_ocs_count)) { ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Tip {1} nicht gefunden", this.BookieName, tipp.BookieMatchName)); throw new BookieTippNotFoundExcpetion(String.Format("Bookie {0}: Tip {1} nicht gefunden", this.BookieName, tipp.BookieMatchName)); } if (total_ocs_count <= 0) { ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Tip {1} nicht gefunden", this.BookieName, tipp.BookieMatchName)); throw new BookieTippNotFoundExcpetion(String.Format("Bookie {0}: Tip {1} nicht gefunden", this.BookieName, tipp.BookieMatchName)); } /* else if (total_ocs_count > 1) { throw new BookieMoreThanOneFoundExcpetion(String.Format("Bookie {0}: Tipp {1} nicht eindeutig", this.BookieName, tipp.BookieMatchName)); } */ ConsoleWriter.writeToConsole(String.Format("Bookie {0}: Tip {1} gefunden", this.BookieName, tipp.BookieMatchName)); _status = BookieStatusType.GETMARKET; } } } _mreWaitForAction.Set(); } }
Has anybody an Idea what's happens here?
Thanks
lichtbringer
By doing
_webBrowser.Invoke(new delegatePlaceBet(searchTipp), new object[] { tipp })
, you make thesearchTipp
method be executed synchronously on the main UI thread. This is understood, as you cannot accessWebBrowser
API from any thread other than the original thread the control was created on.However, by doing so, the
_mreWaitForAction.WaitOne(10000)
call will be too executed on the main UI thread, effectively blocking the message loop (which was started byApplication.Run
).WebBrowser
needs a functional message loop, which is constantly pumping Windows messages, otherwiseDocumentCompleted
doesn't get fired, and you are getting a deadlock.My understanding is, you only create another thread here to organize the workflow of your automation scenario. You really don't need another thread for that. Here's an example of how it can be done on the main UI thread asynchronously, using
async
/await
, and here's an example of usingWebBrowser
in a console app.Alternatively, as a workaround, you can use
WaitWithDoEvents
from here, like this:_mreWaitForAction.WaitWithDoEvents(10000)
. It waits for a handle while still pumping messages. You should be aware of potential implications of creating a nested message loop withApplication.DoEvents()
.Note, you still don't need a separate thread if you use a nested message loop. It will give you the linear code workflow on your main UI thread. Example:
Although as I've mentioned above, you can achieve the same more naturally and without nested message loops, using
async
/await
, which is a preferred way of doing this, IMO.