使用TaskScheduler.FromCurrentSynchronizationContext更新Task中的UI

时间:2013-07-02 05:47:39

标签: c# multithreading c#-4.0 task

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

问题出在哪里?

谢谢:)

4 个答案:

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