Thread - How to use it without freezing screen?

Asked

Viewed 2,403 times

1

I have a system that goes through some html elements, collecting some links. I would like to wait for a while, but whenever I use Thread.Sleep it freezes my program for the set time. I have used Application.Doevents(); and Thread Join. and none of them solved.

And every time I put Sleep inside Thread it doesn’t work because they fire one after the other, I think the right way should be to wait for one Thread to finish another, but how to do that?

Every link I visit I create a thread, according to the code below.

Edit: Program flow Windows Form -> I have a loop with N repetitions, inside this loop I visit a web page and collect some links, every time I enter a link I create a Thread and at the end of it I kill it with Application.Exitthread(); But I would like to take a 1 minute break each url visit, but when using Sleep my form gets stuck and I would not like this behavior. Remembering that at the end of the loop he must have already created dozens of Threads.

private void runBrowserThread(Uri url)
    {
        var th = new Thread(() =>
        {

            label5.Invoke((MethodInvoker)(() => label5.Text = url.ToString()));
            //.Sleep(int.Parse(txtTempoPaginas.Text)*1000);

            var br = new WebBrowser();
            br.DocumentCompleted += browser_DocumentCompleted;
            br.Navigate(url);
            Application.Run();


        });

        th.SetApartmentState(ApartmentState.STA);
        th.Start();
        th.Join(5000);

    }

I’m using the following 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
    }
}
  • We’re talking about a Windows Forms/WPF project, right? And what’s the real reason for your wait, depends on some condition or the answer to something? Please describe a little better the flow your program should follow.

  • Edit: Program flow Windows Form -> I have a loop with N repetitions, inside this loop I visit a web page and collect some links, every time I enter a link I create a Thread and at the end of it I kill it with Application.Exitthread(); however, I would like to take a 1-minute break from each url visit,.

1 answer

2

There are different ways to solve the problem of waiting for a while without crashing the GUI thread.

1) System.Timers.Timer, for . NET Framework 2.0+

Since you have a constant and predefined period, a Timer can warn you every X time when the action should be performed:

private System.Timers.Timer timer;

private void runBrowserThread(Uri url)
{
    timer = new System.Timers.Timer(2000);
    timer.Elapsed += OnTimerElapsed;
    timer.Start();
}

private void OnTimerElapsed(Object source, System.Timers.ElapsedEventArgs e)
{
    // ação individual

    // quando você souber que chegou ao fim...
    if(end)
    {
        timer.Stop();
    }
}

2) Task Delay., for . NET Framework 4.5+

If you have the option to opt for a newer technique, you can opt for async/await to create a Task that waits for a certain time. By working with asynchrony, your GUI thread should continue running normally:

private async Task runBrowserThread(Uri url)
{
    // espera até que a Task complete o Delay
    await Task.Delay(5000);

    // continua executando
}

=========================================================================

Edited: my first answer by itself did not solve the user problem, but I kept it as it can serve as reference for anyone looking for ways not to freeze the screen in Windows Forms applications.

Considering that by your example you want to make a loop and wait a while between each iteration without locking the Windows Forms window, I believe that there is no practical way to do it with the older libraries of Threads manipulation. Because of that, I will use the Task Parallel Library (TPL), which is just . NET 4.0+ to get better at working.

I would solve this problem by turning the have-navigate part and take the result of navigation in one step just for your iteration. For this, I would create a method like Navigateasync():

/// <summary>
/// Classe estática apenas para tornar mais bonita a chamada ao método assíncrono para quem executa a tarefa, poderia ser um método qualquer dentro do próprio Form ou uma classe separada
/// </summary>
public static class WebBrowserExtension
{
    /// <summary>
    /// Carrega os resultados de uma navegação no WebBrowser através de um método assíncrono
    /// </summary>
    /// <param name="webBrowser">Instância de WebBrowser</param>
    /// <param name="urlString">Caminho da URL a ser navegada</param>
    /// <returns></returns>
    public static async Task<WebBrowserDocumentCompletedEventArgs> NavigateAsync(this WebBrowser webBrowser, string urlString)
    {
        // precisamos dele para controlar o nosso contexto assíncrono, que não teríamos iniciando Threads
        var taskCompletionSource = new TaskCompletionSource<WebBrowserDocumentCompletedEventArgs>();

        WebBrowserDocumentCompletedEventHandler handler = delegate(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            // indicamos o fim do TaskCompletionSource retornando o argumento do evento
            taskCompletionSource.SetResult(e);
        };

        webBrowser.DocumentCompleted += handler;

        webBrowser.Navigate(urlString);

        // com o await, podemos esperar que o evento DocumentCompleted nos retorne os valores
        var navigationResult = await taskCompletionSource.Task;

        // é interessante desregistrar esse evento, porque caso contrário ao longo do loop vamos cair várias vezes no handler para Tasks já finalizadas
        // se não desregistrarmos aqui, teríamos que verificar por taskCompletionSource.Task.IsCompleted até encontrarmos a chamada ao Task que estamos manipulando de fato
        webBrowser.DocumentCompleted -= handler;

        return navigationResult;
    }
}

I made some comments on the code, but for those who don’t have much familiarity with the TPL, we basically use the Taskcompletionsource to manipulate our Thread in a more controlled way. With it, we can define that we should wait for the call to Setresult() to finish the execution, returning a reference to Task to be used with async/await by others .

To solve the flow you described, I used as an example a Windows Forms button click event method:

/// <summary>
/// Método que inicia o processamento. Não precisa ser um evento de clique de botão, basta ser um método assíncrono para iniciar a verificação
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void btnIniciar_Click(object sender, EventArgs e)
{
    // como você disse que está em um loop, vamos exemplificar em um
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(String.Format("{0} Iniciando i=[{1}]", DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), i));

        // navegamos e esperamos a resposta, mas mantendo uma escrita síncrona
        var navigationResult = await webBrowser1.NavigateAsync("http://www.google.com");

        // só por causa da forma como você fez no DocumentCompleted, comparando os dois
        Console.WriteLine(String.Format("{0} i=[{1}] {2} {3}", DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), i, webBrowser1.Url.ToString(), navigationResult.Url.ToString()));

        // força a espera por uns 2 segundos
        await Task.Delay(2000);
    }

    Console.WriteLine(String.Format("{0} Acabou!", DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss")));
}

Running this example, I had an output:

02/02/2016 12:02:26 Iniciando i=[0]
02/02/2016 12:02:29 i=[0] https://www.google.com/?gws_rd=ssl https://www.google.com/?gws_rd=ssl
02/02/2016 12:02:31 Iniciando i=[1]
02/02/2016 12:02:34 i=[1] https://www.google.com/?gws_rd=ssl https://www.google.com/?gws_rd=ssl
02/02/2016 12:02:36 Iniciando i=[2]
02/02/2016 12:02:38 i=[2] https://www.google.com/?gws_rd=ssl https://www.google.com/?gws_rd=ssl
02/02/2016 12:02:40 Iniciando i=[3]
02/02/2016 12:02:43 i=[3] https://www.google.com/?gws_rd=ssl https://www.google.com/?gws_rd=ssl
02/02/2016 12:02:45 Iniciando i=[4]
02/02/2016 12:02:47 i=[4] https://www.google.com/?gws_rd=ssl https://www.google.com/?gws_rd=ssl
02/02/2016 12:02:50 Acabou!

Because we use async/await in the button click method, you may notice that the method does not block the Graphical Interface Thread at any time, and at the same time we can give a waiting time while running the loop.

  • Diego, It didn’t work very well because I use webbrowser and I have an event when I load the page click on some link inside the event: var br = new Webbrowser(); br.Documentcompleted += browser_DocumentCompleted; br. Navigate(url); Oq occurs is that never ends this event it makes thousands of shots, in the old code I used Application.Exitthread(); and it ended. If I put Application.Exitthread(); it now closes my program.

  • Which of the two cases did you try and how did it look? If possible, edit your question with the code you are trying. I didn’t understand the need for that creation of threads within the method, but the 2 cases at first should work independently of that.

  • I’m using async Task How would I create many Threads? Example I have a For with 200 pages every time I step inside that call private async Task runBrowserThread(Uri url) ? My problem is that I’m creating several Threads but at the end it doesn’t end up calling always the same URL, even if I pass different urls.

  • @William edited my answer, because at the end of the day, she alone couldn’t solve your problem. Please give me feedback if I understand your flow correctly and if I can explain my solution.

  • Thanks for the help I’m studying your code to see if I can do what I need to do. I just don’t understand something, I’ll try to explain it better. I have several pages I need to simulate a user action that does the following: Access the site, write an input and click on a button. All this already works but the site after several attempts blocks me and only releases after 1 hour. That’s why it needs Sleep. Ah and big move is in webbrowser.Documentcompleted += browser_coment_DocumentCompleted 'Cause it’s only after I’ve loaded the page that I can click the button.

  • If you feel better I can send you the application by email

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.