3

I am trying to create a while or for loop that display an indexed string or int in order using a thread, so that it changes to the next index automatically. This is what I have so far. It is in c# but plan to use the same information for java.

private void button1_Click(object sender, EventArgs e)
{
    string st = "hello my name is miroslav glamuzina";
    string[] arr = st.Split(' ');
    int i = 0;
    while (i < arr.Length)
    {
        Thread.Sleep(50);
        label1.Text = arr[i];
        i++;
    }
}
Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • Are you using a background thread -- as Servy pointed out, it looks like you are just updating the label on your main thread. – JMarsch Mar 12 '14 at 02:08
  • yeah, i believe that is what i am doing but what is the difference between c reating it on a new thread as opposed to using the main thread? –  Mar 13 '14 at 20:06
  • The main thread is responsible for actually repainting the label in response to your change, but it doesn't do that "inline" when you set label.Text. Windows UI is a message based system, so when you set label.Text, you are actually queuing a message to the label to repaint itself. However, that message will only be dequeued when your click handler returns, because that's the thread that services messages. So, you are racking up all those change messages, but windows can't act on them until your event handler returns. Have a look at this: http://www.winprog.org/tutorial/message_loop.html – JMarsch Mar 13 '14 at 20:20
  • By the way, that's the same reason that your window is unresponsive while you are looping in the main (can't be dragged/minimized/etc). All those mouse operations are messages as well, and the thread that would normally be acting on them is busy running your loop. Running a background thread, or otherwise making it asyc (as in Servy's answer, which was great) allows the main thread to go back to the message queue and continue processing messages. – JMarsch Mar 13 '14 at 20:31

4 Answers4

3

The TPL plus await makes this really easy:

private async void button1_Click(object sender, EventArgs e)
{
    string st = "hello my name is miroslav glamuzina";
    string[] arr = st.Split(' ');
    foreach(string word in arr)
    {
        label1.Text = word;
        await Task.Delay(50);
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
2

Another option you have is to use Microsoft's Reactive Extension (Rx) to handle the asynchronicity for you.

Here's what your code could look like:

private SerialDisposable _subscription = new SerialDisposable();

private void button1_Click(object sender, EventArgs e)
{
    string st = "hello my name is miroslav glamuzina";
    string[] arr = st.Split(' ');

    _subscription.Disposable = 
        Observable
            .Interval(TimeSpan.FromMilliseconds(50.0))
            .Select(i => arr[(int)i])
            .Take(arr.Length)
            .ObserveOn(this)
            .Subscribe(word =>
            {
                label1.Text = word;
            });
}

Now, the nice thing about this code is that the Subscribe method returns an IDisposable that can be used to stop the observable query before it finishes. Sort of like a cancellation request. Now by coupling this with a SerialDisposable you get the nice behaviour that if your user clicks the button multiple times then only the latest subscription to the observable query will run and if an existing subscription is running it will be stopped first.

Rx also handles pushing code to a background thread without you neding to think about it. The ObserveOn(this) call brings the thread back to running on the UI so that the label can be updated.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • it's a good thing that you know about Rx but i'm sure topic starter didn't heard about that and by the code he showed he would not understand how it works. – Vasily Semenov Mar 13 '14 at 00:26
  • 1
    @VasilySemenov - I think I described the two methods that required a bit more explanation. The rest are all similar to standard `IEnumerable` linq and the `Interval` method should be fairly obvious. I would expect the OP to be able to get, at least, some understanding from my answer. – Enigmativity Mar 13 '14 at 01:04
  • +1 for Rx. While it might be an overkill for this simple task, the concept itself is very powerful. – noseratio Mar 13 '14 at 02:25
1

This code works in the UI thread so Thread.Sleep(X) sleeps the main application thread. I would recommend you use a BackgroundWorker.

private void button1_Click(object sender, EventArgs e)
{
    System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
    bw.DoWork += (sender, e) => 
    {
        string st = "hello my name is miroslav glamuzina";
        string[] arr = st.Split(' ');
        System.ComponentModel.BackgroundWorker worker = sender as System.ComponentModel.BackgroundWorker;
        for (int i = 0; i < arr.Length; i++)
        {                
            Thread.Sleep(500);
            worker.ReportProgress(i, arr[i]);
        }
    };
    bw.ProgressChanged += (sender, e) =>
    {
        label1.Text = e.UserState as string;
    };
    bw.RunWorkerAsync();
}
Vasily Semenov
  • 302
  • 3
  • 6
1

Using a thread is really redundant here. I too think Task.Delay is the best approach if you can use C# 5.0 async/await. Yet as an alternative, here is another approach using yield and a timer, also based on the compiler-generated state machine. It works with C# 2.0 and later:

private void button1_Click(object sender, EventArgs e)
{
    this.button1.Enabled = false;
    Iterate();
}

IEnumerable GetIterator()
{
    string st = "hello my name is Noseratio";
    string[] arr = st.Split(' ');
    foreach (var s in arr)
    {
        label1.Text = s;
        yield return Type.Missing;
    }
}

void Iterate()
{
    var enumerator = GetIterator().GetEnumerator();
    var timer = new System.Windows.Forms.Timer();
    timer.Interval = 1000;
    timer.Tick += delegate
    {
        if (!enumerator.MoveNext())
        {
            timer.Dispose();
            this.button1.Enabled = true;
        }
    };
    timer.Start();
}
noseratio
  • 59,932
  • 34
  • 208
  • 486