update UI in Task using TaskScheduler.FromCurrentSynchronizationContext
Solution 1
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.
Solution 2
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);
}
});
Solution 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
Arian
Please vote-up this thread: RDLC Report Viewer for Visual Studio 2022
Updated on June 04, 2022Comments
-
Arian almost 2 years
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 :)
-
Arian almost 11 yearsthanks but I'm currently using
C#-4
what can I do?In my main program I have not useSleep
I have some methods that arelong running
and I add Item after execute of them in list box such asStep 1 Completed...
but this message does not show until running -
Jon Skeet almost 11 years@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.
-
Admin about 9 yearsPlease see why
while(true)
andThread.Sleep
together are "evil", and you are also lacking aCancellationToken
or anything alike to control a break-scenario. -
Jay about 9 yearsThe 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.
-
Ignacio Soler Garcia almost 7 yearsThis should be the accepted answer in my humble opinion
-
nflash over 6 yearsI 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.
-
Jay about 6 yearsThe 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.
-
Chris Moschini over 5 yearsIf you happen to be working on a Xamarin cross-platform mobile project, the equivalent command is
Xamarin.Forms.Device.BeginInvokeOnMainThread
-
AlvinfromDiaspar about 4 years@IgnacioSolerGarcia - But i dont believe this solves the problem. The list of items is being updated by the UI thread, BUT, the thread.sleep is still going to make the UI freeze.
-
Ignacio Soler Garcia about 4 years@AlvinfromDiaspar: Sleep(1000) is executed on a Task so I do not see how it could block the UI. Have you tried the code?