问题
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 :)
回答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.
回答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);
}
});
回答3:
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.
回答4:
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
来源:https://stackoverflow.com/questions/17418208/update-ui-in-task-using-taskscheduler-fromcurrentsynchronizationcontext