使用TaskScheduler.FromCurrentSynchronizationContext更新Task中的UI

我想使用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被锁定。

问题出在哪儿 ?

谢谢 :)

哪里有问题?

那么你明确地说你要在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在不同的线程上模拟长时间运行的任务(此时正在hibernate),并使用ReportProgress返回UI线程以添加列表项。

您可以在线程中完成所有工作,但是当您需要更新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); } }); 

基于此主题中的建议,我提出了以下解决方案:

  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。

为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(); 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生成的代码非常相似(如果不相等)