我想使用Task
向列表框添加一些文字,我只需使用一个按钮并在点击事件中放置此代码:
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);
但它不起作用,并且在for循环结束之前UI被锁定。
问题出在哪里?
谢谢:)
答案 0 :(得分:6)
您可以在线程中完成所有工作,但是当您需要更新UI时,请使用调度程序:
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);
}
});
答案 1 :(得分:4)
哪里有问题?
嗯,你明确表示要在UI线程中执行任务 ...然后你在任务中睡觉,所以它阻止了UI线程。您是如何期望进入UI线程的,但是Thread.Sleep
而不是导致问题?
如果您可以使用C#5和async / await,那将使事情变得更加容易:
private static async Task ShowCitiesAsync()
{
for (int i = 0; i < 10; i++)
{
listBox1.Items.Add("Number cities in problem = " + i);
await Task.Delay(1000);
}
}
如果您不能使用C#5(根据您的标签建议),则会非常棘手。您可能最好使用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();
正如您所看到的,它非常难看......它假设您不希望在UI更新之间实际执行任何工作。
另一个替代方法是使用BackgroundWorker
在不同的线程上模拟长时间运行的任务(暂时休眠),并使用ReportProgress
返回UI线程以添加列表项
答案 2 :(得分:-1)
根据本主题中的建议,我提出了以下解决方案:
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是主窗体之外的类中的构造函数。它需要引用需要更新的表单,以及从中获取数据所需的任务的参考。
form1.UpdateGUI方法将您想要设置的任何数据设置为form1,然后将其设置为form1。
答案 3 :(得分:-1)
为c#4.0提供另一种解决方案。这类似于@Debora和@Jay(好吧,如果你忘记了while(true)...只是谈论BeginInvoke)解决方案,但完全基于TPL和更接近使用async /生成的代码的解决方案等待:
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);
您的工作任务应使用默认的TaskScheduler(将使用ThreadPool)进行调度,并在需要更新UI时使用uiScheduler回调到UI线程。 请记住,这是一个示例实现,此示例可能存在一些问题,例如,内部任务计划在UI线程上执行,但调用任务不会等待它,因此睡眠将实际运行内部任务正在运行。 您不等待任务也很重要,否则您可能会遇到死锁(内部任务正在尝试在等待外部任务的UI线程上运行)。
我通常在继续任务上使用uiScheduler来向UI提供数据。在你的情况下,它可能是这样的:
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);
生成的编译代码与使用async / await生成的代码非常相似(如果不相等)