开发者

Simple Thread Programming

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!


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:

  • Remember to set WorkerReportsProgress in the designer if you want the progress to work.
  • When using a BackgroundWorker it is also often useful to use the ProgressBar control.
  • If you want to be able to cancel the background worker, that is possible too. See CancelAsync and WorkerSupportsCancellation.
  • When the background worker completes it fires the RunWorkerCompleted event.


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.


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();
    }
}


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.


After Thread.Sleep, try this:

this.Update();


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.


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;
        }
    }
}


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);
}


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;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜