我想使用
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 循环结束。
问题出在哪里?
您可以在线程中完成所有工作,但是当您需要更新 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);
}
});
问题出在哪里?
好吧,您明确表示您想要在 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 更新之间实际执行任何工作。
另一种选择是使用 在不同的线程上模拟长时间运行的任务(目前正在睡眠),并使用ReportProgress
返回 UI 线程以添加列表项。
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 生成的代码非常相似(如果不相等)
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。