Brent Allard
Brent Allard

Reputation: 396

C# Form Controls Won't Update with Multithreading

I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.

I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:

Here's the 'main' program:

class Program
{
    static MyForm form;

    static void Main(string[] args)
    {
        form = new MyForm();
        form.Show();

        doWork();

        form.Close();
    }

    //arbitrary example of processing that takes some period of time
    static void doWork()
    {
        while (form.Value < 100000) 
        {
            form.ChangeVal();
            Thread.Sleep(1);
        }
        return;
    }
}

...And here's the Form. I'm not including the auto-generated stuff from VS.

public partial class MyForm : Form
{
    private int val;
    public int Value
    {
        get { return val; }
        set { val = value; }
    }

    public Thread GUIupdater;


    public MyForm()
    {
        InitializeComponent();
        this.Refresh();
    }

    private void MyForm_Load(object sender, EventArgs e)
    {
        GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
        GUIupdater.Start();

        this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
    }

    public void ChangeVal()
    {
        val++;
    }

    private void changeLabel(string s)
    {
        label.Text = s;
        label.Refresh();
    }
    private delegate void labelChanger(string s);
    private void GUIupdaterThread()
    {
        while (true)
        {
            Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
            Thread.Sleep(100); //100 ms
        }
    }
    private void killThreadOnClose(object sender, FormClosingEventArgs e)
    {
        GUIupdater.Abort();
    }
}

So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!

Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here

Edits:

CLARIFICATIONS:

About my 'actual' project:

Upvotes: 0

Views: 1724

Answers (4)

Chris Dunaway
Chris Dunaway

Reputation: 11216

I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.

Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:

This form has a single label on it.

public partial class ProgressForm : Form
{

    private int currentValue = 0;

    public ProgressForm()
    {
        InitializeComponent();
    }

    public void ChangeValue(int newValue)
    {
        currentValue = newValue;
        lblValue.Text = string.Format("Current value: {0}", currentValue);
        lblValue.Refresh();   //Call Refresh to make the label update itself
    }
}

static class Program
{
    private static ProgressForm progressForm = null;

    [STAThread]
    static void Main()
    {
        //Application.EnableVisualStyles();
        //Application.SetCompatibleTextRenderingDefault(false);
        //Application.Run(new Form1());

        progressForm = new ProgressForm();
        progressForm.Show();

        doWork();

        progressForm.Close();
    }


    //arbitrary example of processing that takes some period of time
    static void doWork()
    {
        int i = 0;
        while (i < 100000)
        {
            progressForm.ChangeValue(i);
            Thread.Sleep(1);
            i++;
        }
        return;
    }
}

Upvotes: 1

eracube
eracube

Reputation: 2639

You may use the following instead as you are trying to access UI control other than main thread (from which it is created).

while ( true )
{
    Invoke ( ( Action ) (() =>
    {
       label.Text = val.ToString();
       label.Refresh()
       Application.DoEvents();
    }));

    Thread.Sleep( 100 );
}

Upvotes: 1

Fatih
Fatih

Reputation: 810

I recommend you to use "backgroundworker".

First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.

private void btnDoIt_Click(object sender, EventArgs e)
{
    backgroundWorker.RunWorkerAsync();
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Foo();
}

int total = 0;
private void Foo()
{
    for (int i = 0; i <= 100000; i++)
    {
        total += i;
        this.Text = i.ToString();
    }
}

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Run next process
}

Upvotes: 0

Anton Norka
Anton Norka

Reputation: 2312

Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.

There are several approaches how to update controls from other threads. I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).

Upvotes: 3

Related Questions