2

我想使用添加一些文本到列表框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 循环结束。

哪里有问题 ?

谢谢 :)

4

4 回答 4

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);
  }
});
于 2014-09-21T23:55:11.767 回答
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 线程以添加列表项。

于 2013-07-02T05:50:55.863 回答
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 线程上执行,但调用任务并未等待,因此 sleep 将实际运行当内部任务正在运行时。不要等待任务也很重要,否则可能会出现死锁(内部任务试图在等待外部任务的 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 生成的代码非常相似(如果不相等)

于 2018-01-03T18:47:32.547 回答
-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。

于 2014-11-21T15:35:44.423 回答