0

I have some links in my program which I must open the links in Web Browser one by one and enter some data in Web page.

The links list is provided in GUI Thread. But I need to do some works with links outside of Main Thread to avoid of Lagging and Hanging the GUI. And I need to open links one after the other. I mean if a thread starts to working, the next thread will start only when the thread finish works.

Another important thing about Links list is that when threads are created and working, Links List is updating.

So I created a worker thread which manages starting of another Threads.

I have two AutoResetEvents, one for checking the list that is empty or not (postInQueue). If the list is empty, it's wait until a link will added to list and calls postInQueue.set().

The second one is threadInProgress that when a thread is started working, it's waiting until the thread calls threadInProgress.set();

AutoResetEvent threadInProgress = new AutoResetEvent(False);
AutoResetEvent postInQueue = new AutoResetEvent (False);
List<String> links = new List<String>;
public MainForm(){
InitializeComponent();
    Thread threadManager = new Thread( () => 
    {
        while(true){
            if (postsQueue.Count == 0)
                postInQueue.WaitOne();
            Thread t2 = new Thread(() => {
                linkProcess(links[0]);
            };
            t2.SetApartmentState(ApartmentState.STA);
            t2.Start();
            threadInProgress.WaitOne(60000);
            links.RemoveAt(0);
        }
    });
    threadManager.start();
}
public void linkProcess(String link){
    WebBrowser webBrowser = new WebBrowser();
    webBrowser.DocumentCompleted += (s , e) => {
        //Enter some data in webBrowser
        Application.ExitThread();
        threadInProgress.set();
    };
    webBrowser.Navigate(link);
    Application.Run();
}

I Must call Application.Run() in each thread to DocumentCompleted Event will be called.

This code works well for two or three first links but next threads will be stuck in Application.Run() until threadInProgress.WaitOne(60000); send timeout.

Two first links works correct but then I realized that CPU usage is 0%. When I click on break all, I see that the thread is stucking in Application.Run().

What is the problem?

In a forum, someone advise me to use process except thread... How can this be possible? And would be helpful?

Saman Salehi
  • 1,995
  • 1
  • 22
  • 36
  • 1
    "I Must call Application.Run() in each thread to DocumentCompleted Event will be called." - this seems very, very, very odd to me. This method should probably only be called once in any application. I'm guessing you're confusing the main event loop somehow. – millimoose Jan 31 '16 at 21:27
  • Since WebBrowser is a GUI control, you should also probably work with it on the event loop thread, not on a worker thread. – millimoose Jan 31 '16 at 21:29
  • @millimoose At first, I didn't use Application.Run(), but DocumentEvent didn't fired. So I searched the internet and I found this [answer](http://stackoverflow.com/a/4271581/3786566) – Saman Salehi Jan 31 '16 at 21:32
  • @millimoose And consider that this code works correct for first two repeats... in some rare time, this work all correct... If it's because of WebBrowser, why does work correct in sometimes? – Saman Salehi Jan 31 '16 at 21:33
  • 1
    Sounds like a deadlock issue to me... – Marty Jan 31 '16 at 21:40
  • 1
    @K2evil - What are you trying to do with this? You're entering data into a web page? What's with all the threading? Why not just host the browser on a form? Or maybe, can you do a POST with `WebClient`? Your nested thread creation just seems like a disaster waiting to happen. – Enigmativity Feb 01 '16 at 01:20
  • @K2evil "It works correctly sometime" means "it doesn't work correctly," especially in any code involving threads. It means your basic approach is fundamentally broken, but manages to soldier on by lucky coincidence of a race condition not occuring. I'm not sure I have anything more specific to add, my approach would be to completely rearchitect your concurrent code, get rid of the low-level signalling and events, and use the appropriate classes from, say, `System.Collections.Concurrent` to implement moving tasks between threads. Or `async`/`await`, keeping a UI responsive is what it's for. – millimoose Feb 01 '16 at 10:05
  • I mean you already have a GUI with its own event loop running, it makes no sense to start a new one just for the Web Browser control, and I'm guessing they're clashing somehow. (I don't know whether or not WinForms allows for running independent event loops in different threads, but it seems excessive.) The answer you linked seems to mostly talk about using a WebBrowser control in a console app context. – millimoose Feb 01 '16 at 10:07

3 Answers3

1

All of this thread stuff is insane - especially threads that invoke threads and then calling Application.Run to stop threads exiting. It's just overkill.

Here's what I would use:

string uri = "https://www.mywebsite.com/customer/account/loginPost/";
string html;

using (var wc = new WebClient())
{
    wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";

    var nvc = new System.Collections.Specialized.NameValueCollection()
    {
        { "form_key", "BaqsPklZGXt3Kq5o" },
        { "login[username]", "user@domain.com" },
        { "login[password]", "myfunkypassword" },
        { "send", "" },
    };

    byte[] result = wc.UploadValues(uri, nvc);

    html = System.Text.Encoding.UTF8.GetString(result);
}

This can all be wrapped up as as async call, or just pushed to a normal background worker thread. I would like to know precisely what you're trying to do before I would try turning this into a method - that would mean seeing the code that you're currently using.

You would need to figure out what name-value pairs you need. I use a Chrome extension called "Request Maker". It puts a little envelope icon on the address bar that you can click after performing some action on a page. It lets you see the requests that were made and to get all of the parameters out.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • I tested login page with Request Maker. there is two name-value pair that is for username and password. but there is another name-value which is ("btnSubmit","Login"). It's for login button... It means if I upload this name-value, It's equal to I click on this button? – Saman Salehi Feb 01 '16 at 11:29
  • @K2evil - Yes, it should be. You should try it. That's what I did when testing. – Enigmativity Feb 01 '16 at 11:36
  • It didn't work... because each time I want to send a new post to weblog, It's create 2 random string named "__VIEWSTATE" and "__EVENTVALIDATION" which are created in server side. I try deleting and editing this fields but it cause to logout from weblog – Saman Salehi Feb 01 '16 at 12:09
  • It's a free blog... somthing like blogger.com... but there is no api available for this blog.. the site is www.blogfa.com, but it's in Persian language – Saman Salehi Feb 01 '16 at 13:53
  • Imagine there is a news agency website ex. BBC News. I'm writing a program that (1) every 30 minutes, reads RSS feed of site and (2) finds last news in past 30 minutes and then (3) parses the webpage of news to find news title, news content and tags. (4) then adds these to something like blogger. that's what I do in my code. I completed stage 1, 2, 3 but stuck in stage 4... – Saman Salehi Feb 01 '16 at 17:44
  • After you said about why `WebBrowser.DocumentComplete` isn't called in another thread and why `Application.Run` is called, I thought about a trick that keep the thread alive... And that is using another thread :D I posted the answer... Thank You... – Saman Salehi Feb 05 '16 at 02:03
0

This is architected in a very bad way. Normally, you have one UI thread and one call to Application.Run(). If you want to show new UI forms call into that thread using (for example) Invoke and start the new form there.

This simplifies everything considerably. If you do that a lot of the threading and eventing goes away and your problem disappears in the process.

Application.Run is supposed to block. After all it runs the app until the app is supposed to exit. Normally, you'd pass a form to it. Not sure what your intention is given the code posted. linkProcess probably should place the browser control into a form. Without a form the control cannot appear anywhere.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I think the OP is just trying to use the web browser to POST data to a URL - I don't think he wants to actually display the browser. In a nutshell - I think he's starting a Space Program to fire a toy rocket. – Enigmativity Feb 01 '16 at 01:24
  • As @Enigmativity said, I use don't show webBrowser anywhere, and in `linkProcess` I don't change anything of UI Thread. Only create a WebBrowser, Open a Url ,post some data to it and then close it... I use Application.Run because it seems that `DocumentCompleted` Event is not firing in Non-UI thread if I don't call `Application.Run` – Saman Salehi Feb 01 '16 at 01:42
  • @Enigmativity Do you know any other lightweight tool that I can open a link with that and post some data to web page? Something non-ui... I tried HttpRequest but it's not possible to click a button or set value to input elements whit HttpRequest – Saman Salehi Feb 01 '16 at 01:44
  • 1
    @K2evil - The `DocumentCompleted` event would fire if the thread hadn't already completed - that's what `Application.Run()` is doing, keeping the thread alive. But it is overkill. `WebClient` is probably going to be sufficient for your needs. But I would need to know what page you are posting too and what authentication is required, etc. – Enigmativity Feb 01 '16 at 01:47
  • @Enigmativity I must set value to 2 input elements (For username and Password) and then click a button to login to a site... – Saman Salehi Feb 01 '16 at 01:50
  • @Enigmativity My program is a auto poster for a weblog... It downloads a page, makes some change to page, and then login to weblog control panel by inputing username and password, and then click on a button, then input downloaded page as a new post to weblog... So in all process, filling forms and clicking on buttons is important. The reason that I use threads is I should only do only one process in a time, and i should wait until a post finish, but don't stop the main thread – Saman Salehi Feb 01 '16 at 02:19
  • @K2evil - I've posted an answer. – Enigmativity Feb 01 '16 at 02:38
  • @K2evil - doesn't said weblog have an API like most of them seem to for submitting posts? This seems needlessly convoluted. – millimoose Feb 01 '16 at 10:12
  • 1
    @K2evil if possible you should use HttpWebRequest or HttpClient to post the exact data required. Use Fiddler to see what that is. If you need to drive a browser use Awesomium which is a .NET hosted Chrome browser. Don't use a GUI component. – usr Feb 01 '16 at 11:19
  • @Enigmativity if you want to continue to use WebBrowser my answer still stands: Your problems will go away if you implement the changes requested. Make use of a single GUI thread to drive the browser. Since this is a GUI component it *requires* a UI thread. Application.Run makes your thread a UI thread, not sure if you realize that. – usr Feb 01 '16 at 11:21
  • @usr - I'm not the OP. I understand how `Application.Run()` works. I think the OP wants to do these updates silently. – Enigmativity Feb 01 '16 at 11:27
  • @Enigmativity just used the wrong @ address, meant the OP. Confusing comment thread. – usr Feb 01 '16 at 11:31
0

Ok... I coudn't find out the reason of hanging when calling Application.Run(). I searched internet and all I found was some questions about Application.Run() with different scenarios but without answer!

But at least I found a way to use WebBrowser.DocumentComplete event in another thread without using Application.Run()!

I start a Thread insist of Application.Run() which run until WebBrowser.DocumentComplete event will be call and finish it works!

Thread t1 = new Thread ( () => {

    AutoResetEvent keepMeAlive = new AutoResetEvent( False );

    webBrowser1.DocumentComplete += (s , e) => {
        //Do Some Stuff

        keepMeAlive.set();
    };

    webBrowser1.Navigate("www.google.com");

    Thread keepAlive = new Thread ( () =>{
        keepMeAlive.WaitOne();
    });
    keepAlive.Start();
});
Saman Salehi
  • 1,995
  • 1
  • 22
  • 36
  • This is a little bit insane. You should really persevere with the `WebClient` approach. – Enigmativity Feb 05 '16 at 03:09
  • @Enigmativity I know, but if I was not able to click on links, It cause to redirecting to login page again! I had to use webBrowser... It's a option when there is few choices... – Saman Salehi Feb 05 '16 at 16:25
  • You should always be able to use `WebClient`. It's just a matter of getting the parameters right. Use something like Firebug in Firefox or Requestion Maker in Chrome to figure out the calls. All this thread stuff is insane. – Enigmativity Feb 05 '16 at 23:15