views:

190

answers:

10

I have started to play with threads in c#, but need now help, here is my code:

public partial class Form1 : Form
{

    public Form1()
    {
        InitializeComponent();

        DoCount();

    }
    public void DoCount()
    {
        for (int i = 0; i < 100; i++)
        {
            objTextBox.Text = i.ToString();
            Thread.Sleep(100);
        }

    }
}

its a simple win forms with a textbox, i want to see the "counting", but as you see in my code, the textbox shows me 99, it count till 99 and then shows up.. i`ll think, i have to manage this in a new thread but dont know how!

A: 

Don't call DoCount directly, call ThreadPool.QueueUserWorkItem(DoCount). This will run DoCount in a new thread. (There are other ways that also work, but this is the simplest that works well.)

The next problem is that you can't directly set the textbox from the thread.

To solve this, use code similar to:

if (this.textBox1.InvokeRequired)
{   
    SetTextCallback d = new SetTextCallback(SetText);
    this.Invoke(d, new object[] { text });
}

See http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx for the full example.

Steven Sudit
+6  A: 

Use a BackgroundWorker. There is a BackgroundWorker overview on MSDN.

Here is an example of how your code might look:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
        for (int i = 0; i < 100; i++)
        {
            backgroundWorker.ReportProgress(i);
            Thread.Sleep(100);
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        textBox1.Text = e.ProgressPercentage.ToString();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }
}

Other notes:

Mark Byers
You can use BackgroundWorker or Thread or (as in my example) ThreadPool, but you still have to deal with the issue of `InvokeRequired`. BackgroundWorker has two events that could be used to set UI controls, but that's not a very flexible solution if you have multiple fields.
Steven Sudit
Thanks for adding the example. Now it's clear that using ProgressChanged to "smuggle" the string value works but is hard to extend. Consider the case where the background thread has to update multiple fields.
Steven Sudit
A: 

After Thread.Sleep, try this:

this.Update();
Ian P
Again, this is not multithreaded and it's not a good idea.
Steven Sudit
This will update the window, but it will not process user input, and (without wishing to totally baffle to OP, but it has to be said) it will still block DDE broadcasts for 100 seconds making launching excel slow, and it will still stop you from shutting the PC down until the app is done..... and all the other reasons you mustn't block your UI thread without pumping messages.
Stewart
+2  A: 

This might be what you are looking for:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        DoCount();
    }
    public void DoCount()
    {
        Thread t = new Thread(new ThreadStart(delegate
        {
           for (int i = 0; i < 100; i++)
           {
               this.Invoke((Action) delegate { objTextBox.Text = i.ToString(); });
               Thread.Sleep(1000);
           }
        }));
        t.IsBackground = true;
        t.Start();
    }
}

Notes

  1. Uses a basic Thread not a BackgroundWorker
  2. Uses Invoke to update the textbox on the UI thread
  3. Sets IsBackground to true so the program exits if the form is closed before the loop is done.
Alan Jackson
This works. Should you be checking InvokeRequired or can we be certain that it will always be true?
Steven Sudit
It should be always true since we are always on a new thread. There is never a case where the inside thread is the same as the outside (UI) thread.
Alan Jackson
+1  A: 

You may want to try out the SynchronizationContext to do this.

Here's a quick example I threw together a while back:

public partial class Form1 : Form
{
    private SynchronizationContext c;
    private Thread t;
    private EventWaitHandle pause =
        new EventWaitHandle(false, EventResetMode.ManualReset);

    public Form1()
    {
        this.InitializeComponent();
        this.c = SynchronizationContext.Current;
    }

    private void Form1Activated(object sender, EventArgs e)
    {
        this.t = new Thread(new ThreadStart(delegate
        {
            this.pause.Reset();
            while (this.t.IsAlive && !this.pause.WaitOne(1000))
            {
                this.c.Post(
                    state => this.label1.Text = DateTime.Now.ToString(),
                    null);
            }
        }));
        this.t.IsBackground = true;
        this.t.Start();
    }

    private void Form1Deactivate(object sender, EventArgs e)
    {
        this.pause.Set();
        this.t.Join();
    }

    /// <summary>
    /// Button1s the click.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    private void Button1Click(object sender, EventArgs e)
    {
        this.Close();
    }
}
Jesse C. Slicer
This works, but synchronization contexts may well be the most complicated way to do this.
Steven Sudit
A: 

My solution is virtually the same as Mark's. The only difference is I check InvokeRequired in my ProgressChanged event. Here's my sample code:

using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace tester
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            if (!backgroundWorker1.IsBusy)
                backgroundWorker1.RunWorkerAsync();
        }

        /// <summary>
        /// This delegate enables asynchronous calls for setting the text property on a control.
        /// </summary>
        delegate void SetTextCallback(string status);

        private void BackgroundWorker1DoWork(object sender, DoWorkEventArgs e)
        {
            for (var i = 0; i < 100; i++)
            {
                backgroundWorker1.ReportProgress(i);
                Thread.Sleep(100);
            }
        }

        private void BackgroundWorker1ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (label1.InvokeRequired)
                Invoke(new SetTextCallback(SetLabelText), new object[] { e.ProgressPercentage.ToString()});
            else
                SetLabelText(e.ProgressPercentage.ToString());
        }

        private void SetLabelText(string text)
        {
            label1.Text = text;
        }
    }
}
Chuck
@Chuck: If I understand correctly, InvokeRequired will never be true because BackgroundWorker invokes the progress changed event in the main thread.
Steven Sudit
In this example, you're correct.
Chuck
+1  A: 

You don't need a thread to do this kind of thing at all - consider changing your code to be event driven and use a System.Windows.Forms.Timer object to implement your timings. Using timers for this has a huge advantage - it doesn't cost 1MB of memory (a thread does), and you don't need to synchronize them - windows does it for you.

Stewart
Depends on what he's trying to do, which is unclear from the token example. If he's just trying to periodically increment a count, then Timer might be ok. If he's trying to do long-running work in the background, then no.
Steven Sudit
A: 

Multithreading could solve this but for something as simple as this counter it is unnecessary.

Another user recommended this.Update(). This works to make the numbers appear because the UI will redraw itself. But it doesn't address the fact that the window is not responsive (you can't move it around).

The third solution and my recommendation for this particular program is Application.DoEvents(). What this does is tell the underlying native window to execute its ProcessMessages method on the message pool. The message pool contains event messages that Windows has sent to it when the window needed to be redrawn, mouse has moved, the form has been moved, minimized, etc. Those instructions were sent by Windows and have been queued. The problem is that the program will not process them until the UI is idle. You can force it by calling this method.

Application.DoEvents() will yield a window which responds as expected in 100 ms intervals. It may be a tad choppy (threads would be more responsive) but it is very easy to put it in and is often sufficient.

for (int i = 0; i < 100; i++)
{
    objTextBox.Text = i.ToString();
    Application.DoEvents();
    Thread.Sleep(100);
}
Phil Gilmore
This is wrong for the same reason that Update is wrong. It will just take longer to see that it is wrong. If you're going to do it on a single thread like this, use an event driven model and a timer so you can keep your message pump alive.
Stewart
@Stewart, My demo code calls Application.DoEvents() which calls the message pump. .Update() does not. If you disagree, try it. A timer would be fine, but what would you PUT in the timer event to keep the message pump alive?
Phil Gilmore
A: 

Here's my shot at a simple example:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Action countUp = this.CountUp;
        countUp.BeginInvoke(null, null);
    }

    private void CountUp()
    {
        for (int i = 0; i < 100; i++)
        {
            this.Invoke(new Action<string>(UpdateTextBox), new object[] { i.ToString() });
            Thread.Sleep(100);
        }
    }

    private void UpdateTextBox(string text)
    {
        this.textBox1.Text = text;
    }
}
Sorax
A: 

THANK YOU ALL for all the answers!!! i`ll try the different versions :)

mike