Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Threading using invoke, freezing the form

I'm trying to use threads and prevent the program from freezing while the thread is busy. It should show the progress (writing of 0's / 1's) and not just show the result after its done, freezing the form in the meanwhile.

In the current program I'm trying to write to a textbox, and actually see constant progress, and the form can't be affected by the tasks of the other thread.

What I have now is I can write to a textbox with a thread using invoke, but It only shows the result (Form freezes while thread is busy), and the form freezes.

Form image:

enter image description here

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace MultiThreading
{
public partial class MultiThreading : Form
{
    public MultiThreading()
    {
        InitializeComponent();
    }

    Thread writeOne, writeTwo;

    private void writeText(TextBox textBox, string text)
    {
        if (textBox.InvokeRequired)
        {
            textBox.BeginInvoke((MethodInvoker)delegate()
            {
                for (int i = 0; i < 500; i++)
                {
                    textBox.Text += text;
                }
            });
        }
        else
        {
            for (int i = 0; i < 500; i++)
            {
                textBox.Text += text;
            }
        }
    }
    private void btnWrite1_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeOne.Start();
    }

    private void btnWrite2_Click(object sender, EventArgs e)
    {
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));
        writeTwo.Start();
    }

    private void btnClear1_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
    }

    private void btnClear2_Click(object sender, EventArgs e)
    {
        txtOutput2.Clear();
    }

    private void btnWriteBoth_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));

        writeOne.Start();
        writeTwo.Start();
    }

    private void btnClearBoth_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
        txtOutput2.Clear();
    }
}

}

EDIT:

Btw for anyone wondering, I'm new to multithreading and I'm just trying to write a small program to understand the best way to do this.

I understand that my previous invoke didn't realy help because I still wasn't giving the form a chance to update, so its getting there.

Ok so running 1 thread like this works, but still running multiple threads together, won't update the form till after the thread is done.
I've added a thread.sleep() so I can try and clear while writing, to see if I can still use the form.

When writing to 1 textbox I can still clear the screen while writing.
But once I use 2 threads, I can't use the form anymore till the thread completes, and gives the output.

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

(If I'm totally wrong on this I don't mind having to read through some examples/threads, I'm still trying to see what is the best way to do this, besides a backgroundworker)

EDIT 2:

I've reduced the number of invokes by reducing the amount to write, but to increase delay giving the same effect of constant writing, just reducing the load.

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

EDIT 3:

Sumeet's example works using

Application.DoEvents();

(notice the s, .DoEvent doesn't work, typo probably :P), writing multiple strings simultaneously & having them show the progress and not just the result.

So Code update again :)

*Using a new button to create 5 threads that write a random number to both textboxes

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 57; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(5);
                Application.DoEvents();
            }));
        }

    }
private void btnNewThread_Click(object sender, EventArgs e)
    {
        Random random = new Random();
        int[] randomNumber = new int[5];
        for (int i = 0; i < 5; i++)
        {
            randomNumber[i] = random.Next(2, 9);
            new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
            new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
        }
    }
like image 470
Random IT Guy Avatar asked Jan 28 '13 19:01

Random IT Guy


2 Answers

This solution works ! Have checked it.

The problem is you keep telling the UI thread to change the Text, but never letting it have time to show you the updated text. To make your UI show the changed text, add the Application.DoEvents line like this :

textBox.Text += text;
Application.DoEvents();

p.s. : Remove the else block of your If / Else loop, it is redundant, and also as pointed by others there is not any use of creating those 2 Threads as all they are doing is post the message on the UI Thread itself.

like image 131
sumeet Avatar answered Oct 14 '22 19:10

sumeet


You're still performing a single-threaded task, just re-launching it on the UI thread if needed.

for (int i = 0; i < 500; i++){
    string text = ""+i;
    textBox.BeginInvoke((MethodInvoker)delegate()
            {
                textBox.Text += text;
            });
}
like image 26
Sten Petrov Avatar answered Oct 14 '22 18:10

Sten Petrov