3

I want to add some text to list box using Task and I simply use a button and place in click event this code:

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 10; i++)
    {
        listBox1.Items.Add("Number cities in problem = " + i.ToString());
        System.Threading.Thread.Sleep(1000);
    }
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);

but it does not work and UI locked until the end of the for loop.

Where is the problem ?

thanks :)

hdoghmen
  • 3,175
  • 4
  • 29
  • 33
Arian
  • 12,793
  • 66
  • 176
  • 300

4 Answers4

7

You could do all work in the thread, but then when you need to update the UI use the dispatcher:

Task.Factory.StartNew(() =>
{
  for (int i = 0; i < 10; i++)
  {
     Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
        new Action(() => {listBox1.Items.Add("Number cities in problem = " + i.ToString()); }));
     System.Threading.Thread.Sleep(1000);
  }
});
Debora
  • 89
  • 1
  • 4
5

where is problem?

Well you're explicitly saying that you want to execute the task in the UI thread... and then you're sleeping within the task, so it's blocking the UI thread. How did you expect to be in the UI thread, but for Thread.Sleep not to cause a problem?

If you can use C# 5 and async/await, that would make things much easier:

private static async Task ShowCitiesAsync()
{
    for (int i = 0; i < 10; i++)
    {
        listBox1.Items.Add("Number cities in problem = " + i);
        await Task.Delay(1000);
    }
}

If you can't use C# 5 (as suggested by your tags), it's significantly trickier. You might be best off using a Timer:

// Note: you probably want System.Windows.Forms.Timer, so that it
// will automatically fire on the UI thread.
Timer timer = new Timer { Interval = 1000; }
int i = 0;
timer.Tick += delegate
{
    listBox1.Items.Add("Number cities in problem = " + i);
    i++;
    if (i == 10)
    {
        timer.Stop();
        timer.Dispose();
    }
};
timer.Start();

As you can see, it's pretty ugly... and it assumes you don't want to actually do any work between UI updates.

Another alternative would be to simulate your long-running task (sleeping at the moment) on a different thread using BackgroundWorker, and use ReportProgress to come back to the UI thread to add the list item.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • thanks but I'm currently using `C#-4` what can I do?In my main program I have not use `Sleep` I have some methods that are `long running` and I add Item after execute of them in list box such as `Step 1 Completed...` but this message does not show until running – Arian Jul 02 '13 at 05:54
  • @Kerezo: See my edits for two alternatives. But we don't really know what the bigger picture is - presumably you'll want to do things other than sleeping at some point. – Jon Skeet Jul 02 '13 at 05:55
3

Just giving another flavour of the solution for c# 4.0. This is similar to @Debora and @Jay (well, if you forget about the while(true)... just talking about the BeginInvoke) solutions, but fully based on TPL and the one that gets closer to the code generated with async/await:

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 10; i++)
    {
        Task.Factory.StartNew(() =>
        {
             listBox1.Items.Add("Number cities in problem = " + i.ToString());
        }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);

        System.Threading.Thread.Sleep(1000);
    }
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);

Your work task should be scheduled using the default TaskScheduler (that will use the ThreadPool) and use the uiScheduler to callback to the UI thread when required to update the UI. Keep in mind that this is a sample implementation and there could be some problems with this sample, for instance, the inner task is scheduled to execute on the UI thread, but it is not waited by the calling task, so the sleep will actually run while the inner task is running. It is also very important that you do not wait for the tasks, or you could have a deadlock (inner task is trying to run on the UI thread that is blocked waiting for the outer task).

I usually use the uiScheduler on continuation task to provide the data to the UI. In your case it could be something like this:

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
    //load data or perform work on background thread
    var itemList = new List<int>();
    for (int i = 0; i < 10; i++)
    {
        itemList.Add(i);
        System.Threading.Thread.Sleep(1000);
    }
    return itemList;
}).ContinueWith((itemList) => 
{
   //get the data from the previous task a continue the execution on the UI thread
   foreach(var item in itemList)
   {
      listBox1.Items.Add("Number cities in problem = " + item.ToString());
   }
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);

The resulting compiled code will be very similar (if not equal) to the code generated with async/await

nflash
  • 430
  • 3
  • 16
-1

Based on the suggestions in this thread, I've come up with the following solution:

    public TaskGUIUpdater(Form1 form1, TaskDataGenerator taskDataGenerator)
    {
        Task.Factory.StartNew(() => {
            while (true)
            {
                form1.BeginInvoke(new Action(() => {
                    form1.UpdateGUI(taskDataGenerator.Counter, taskDataGenerator.RandomData);
                }));

                Thread.Sleep(1000);
            }
        });
    }

TaskGUIUpdater is a constructor in a class outside of the main form. It takes a reference to the form that needs to be updated, as well as a reference of the task that it needs to get data from.

The form1.UpdateGUI method takes whichever data you want to set to form1, and then simply sets it to form1.

Jay
  • 740
  • 4
  • 8
  • 19
  • Please see why [`while(true)` and `Thread.Sleep` together are "evil"](http://stackoverflow.com/questions/8815895/why-is-thread-sleep-so-harmful/8815944#8815944), and you are also lacking a `CancellationToken` or anything alike to control a break-scenario. –  Jan 30 '15 at 11:33
  • The code above does what it needs to. It starts once and runs until the program is closed. A cancellation token is not required, because I never intend to end the task. It's a small bit of code that does not take up any significant resources. It runs in the background and is lightweight. The thread you linked to explains that Thread.Sleep is not very precise and that it takes up 1Mb of memory (= no big deal). The linked-to article explains that it's evil to **not** use Thread.Sleep in a while loop, because the condition check will max out the cpu. Feel free to provide a better solution. – Jay Feb 01 '15 at 17:13
  • I actually didn't notice the while (true) until I read the comments. Why would you want to keep this task running forever? You are actually wasting resources by spinning the cycle every second, scheduling work on the main thread and probably forcing a redraw of the app. Why not just to call the BeginInvoke whenever (and only when) the data (taskDataGenerator.Count and/or taskDataGenerator.RandomData) changes? You would have "real-time" updates (as opposed to wait 1 sec) and would run the update on UI only when the data changes, and therefore avoiding unnecessary redraws of the app. – nflash Jan 03 '18 at 18:55
  • The task needs to run 'forever' because it needs to run all throughout the application's lifetime. I wanted to keep resource usage to a minimum, hence the 1 second delay, which is an eternity in CPU time. I'm sure there are better solutions, but this thing worked when I needed it to. Take it or leave it. I'm no longer involved with the project this was for. – Jay Feb 07 '18 at 13:21