2

example : doing something 9999 time (maybe more than)

for (int i = 1; i <= 9999; i++)
{
    // do something
    label1.content = 100*i/9999 + "%" ;
}

and I want to show percentage of loop on label1 when I compile I can't doing anything several a millisecond and my label show 100% only. someone have any idea sir? thank you.

Saadi
  • 2,211
  • 4
  • 21
  • 50
KPTH
  • 91
  • 1
  • 2
  • 12
  • 7
    Most likely because you're blocking the message loop with your code. If you really want code that runs without blocking the UI (such as blocking updates), you need to run it in the background. – Lasse V. Karlsen Dec 21 '16 at 10:42
  • You mean i can't use code show percentage of loop processing ? – KPTH Dec 21 '16 at 10:50

3 Answers3

3

You can't run a loop on and the update the UI on the same thread simultaneously. That's why you should always perform any long-running work on a background thread and update UI at regular intervals using the dispatcher.

The easiest way to run some code on a background thread is to use the task parallel library (TPL) to start a new task:

Task.Run(()=> 
        {
            for (int i = 1; i <= 9999; i++)
            {
                System.Threading.Thread.Sleep(1500); //simulate long-running operation by sleeping for 1.5s seconds... 
                label1.Dispatcher.BeginInvoke(new Action(() => label1.Content = 100 * i / 9999 + "%"));
            }
        });

my message box immediately show message after i run command while percentage is running

As mentioned the Task is being executed on another thread. So it will run in parallel with the UI thread. That's the reason why the UI can be updated while the loop is running.

You could use the Task.ContinueWith method to show the MessageBox after the task has completed:

int i = 1;
Task.Run(() =>
            {
                for (; i <= 9999; i++)
                {
                    System.Threading.Thread.Sleep(1500); //simulate long-running operation by sleeping for 1.5s seconds... 
                    label1.Dispatcher.BeginInvoke(new Action(() => label1.Content = (i / 9999) * 100 + "%"));
                }
            }).ContinueWith(t =>
            {
              MessageBox.Show("done..." + i.ToString());
            }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I try to use this and I found some problem. 1.I add int number = 0 ; before Task.Run....... 2.In for loop i add command i++; 3.outside Task.Run....... I message box show i 4. message box show i = 0 it should be 9999 – KPTH Dec 21 '16 at 11:15
  • and my message box immediately show message after i run command while percentage is running. – KPTH Dec 21 '16 at 11:19
  • Please see my edited answer. You also might want to do some reading on multi-threading. – mm8 Dec 21 '16 at 11:34
0

thats because you are blocking UI thread while calculating inside your loop. Such things should be made in different thread with callback method.

As a work around, you can import system.windows.forms.dll assembly and call System.Windows.Forms.Application.DoEvents();

    for (int i = 1; i <= 9999; i++)
    {
        System.Windows.Forms.Application.DoEvents();
        // do something
        label1.Content = 100 * i / 9999 + "%";
        Thread.Sleep(10);
    }

But this is a bad practice!

Clemens
  • 123,504
  • 12
  • 155
  • 268
Anton Semenov
  • 6,227
  • 5
  • 41
  • 69
  • Your solution is Ok and it was the regular practice before Tasks, but it needs a reference to System.Windows.Forms in a WPF app. – SERWare Dec 21 '16 at 12:21
  • "it was the regular practice before Tasks", definitely not. Before Tasks we had BackgroundWorker and ThreadPool. – Clemens Dec 21 '16 at 12:30
  • It was a regular practice to avoid block the UI thread without launching new threads as in my solution with Task.Yield() method. – SERWare Dec 21 '16 at 17:19
0

@mm8 solution is ok, but every concurrency round trip to the UI thread from the new one will delay your loop.

If you want the loop ends as early as possible without bloating the UI (and your targeting NET Framework 4.5 or above), you can use the asynchronous Task method equivalent to the old DoEvents: Task.Yield().

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 10000; i++)
        {
            //Do something
            //Simulate work with Task.Delay(millisecondos)
            Label1.Content = (i * 100 / 10000) + "%";
            await Task.Yield(); //give opportunity to dispatch other events
        }
    }

This solution is simple and is an example of why asynchrony doesn't need multithrading always as stated in this answer and this

But there is a trade off between give a break with Task.Yield() and create a new Thread as in @mm8 solution:

  1. Give a break with Task.Yield() is better in case the work can be done in pieces and in each piece the UI must be updated.
  2. Create a new thread is better when UI updates are few (relatively to work done)

NOTE. By the way, in a 10000 loop, I believe is better to show a per thousand progress

Community
  • 1
  • 1
SERWare
  • 392
  • 3
  • 15