如何使用 TaskScheduler.FromCurrentSynchronizationContext 更新任务中的 UI

问题描述 投票:0回答:4

我想使用

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);

但是它不起作用,并且 UI 被锁定,直到 for 循环结束。

问题出在哪里?

c# multithreading c#-4.0 task
4个回答
7
投票

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

5
投票

问题出在哪里?

好吧,您明确表示您想要在 UI 线程中执行任务 ...然后您在任务中休眠,因此它会阻塞 UI 线程。您如何期望进入 UI 线程,但 Thread.Sleep

 
not 会导致问题?

如果您可以使用 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 线程以添加列表项。


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


-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。

© www.soinside.com 2019 - 2024. All rights reserved.