我创建了一个演示版,希望能够证明我过去几天遇到的问题。
我创建了一个名为 数据供应商 的简单类,其中包含一个名为 GenerateRandomInt() 的公共静态方法这是为了模拟一个可能需要一些时间的过程。上述课程的代码可以在下面找到:
class DataSupplier
{
public static int GenerateRandomInt()
{
Random rnd = new Random();
Thread.Sleep(1000);
return rnd.Next();
}
}
我的MainWindow simple包含一个ScrollViewer,其中嵌入了StackPanel,名为 stackPanel1 ,还有一个名为 button1 的按钮这个XAML如下所示:
<Window x:Class="ThreadingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel Name="stackPanel1" />
</ScrollViewer>
<Button Grid.Row="1"
Name="button1"
Content="Generate 10 Labels"
Click="button1_Click" />
</Grid>
</Window>
我想要实现的是,当我点击 button1 生成10个标签时,会使用静态 DataSupplier.GetRandomInt显示一个随机数() 方法。但是我希望它们一个接一个地显示,即只要它们被单独创建。我的 MainWindow 的代码隐藏如下所示:
public partial class MainWindow : Window
{
private BackgroundWorker worker;
public MainWindow()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.IsEnabled = true;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
for (int i = 1; i <= 10; i++)
{
//create a new label, and set it's content to a randomly generated number
Label lbl = new Label();
lbl.Content = DataSupplier.GenerateRandomInt();
//add this label to stackPanel1
stackPanel1.Children.Add(lbl);
}
}));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
worker.RunWorkerAsync();
}
}
然而,正在发生的事情是,在我的所有随机生成的标签已添加到stackPanel1之前,没有任何视觉效果。
无论如何我可以对此进行编程,以便在每个标签创建时显示每个标签吗?同时还要确保我的UI保持响应?
答案 0 :(得分:1)
你的问题是你已经创建了一个后台工作程序来卸载UI线程的工作,但需要花费很长时间GenerateRandomInt()
的操作才会被调度回UI线程,从而导致你的应用程序开始冻结。
有类似的东西:
private void worker_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 1; i <= 10; i++) {
int x = DataSupplier.GenerateRandomInt();
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action)(() => {
Label lbl = new Label { Content = x };
stackPanel1.Children.Add(lbl);
}));
}
}
“长时间运行的功能”在后台线程中运行,只需将UI内容调度到UI线程,从而为您提供所期望的内容。最初我们创建了一个线程,最终发送了我们想要它做的工作,然后回到调用者那里使它变得毫无意义。
现在,如果您可以使用 .net 4.5 ,这可以更加简单,例如:
长时间运行的静态功能:
internal class DataSupplier {
public static async Task<int> GenerateRandomInt() {
Random rnd = new Random();
await Task.Delay(1000);
return rnd.Next();
}
}
并在您的MainWindow的代码隐藏中:
private async void button1_Click(object sender, RoutedEventArgs e) {
button1.IsEnabled = false;
await AddLabels();
button1.IsEnabled = true;
}
private async Task AddLabels() {
for (int i = 0; i < 10; ++i) {
Label lb = new Label { Content = await DataSupplier.GenerateRandomInt() };
stackPanel1.Children.Add(lb);
}
}
这会给你相同的结果,更简单(只是我的意见)
答案 1 :(得分:0)
尝试使用BeginInvoke。
for (int i = 1; i <= 10; i++)
{
string content = DataSupplier.GenerateRandomInt();
this.Dispatcher.BeginInvoke((Action)(() =>
{
lbl.Content = content;
//create a new label, and set it's content to a randomly generated number
Label lbl = new Label();
//add this label to stackPanel1
stackPanel1.Children.Add(lbl);
}));
Thread.Sleep(100);
}
Invoke将同步执行,因此当执行调用时,您的线程将返回,而BeginInvoke将异步执行。 我添加了一个thread.sleep来暂停后台线程一段时间。 在这种情况下,后台线程完成的所有工作都是更新UI,因此UI总是很忙。
答案 2 :(得分:0)
尝试在您的工作人员DispatcherPriority
中将Render
设置为_DoWork
。它应该修复渲染
this.Dispatcher.Invoke((Action)(() =>
{
for (int i = 1; i <= 10; i++)
{
//create a new label, and set it's content to a randomly generated number
Label lbl = new Label();
lbl.Content = DataSupplier.GenerateRandomInt();
//add this label to stackPanel1
stackPanel1.Children.Add(lbl);
}
}), DispatcherPriority.Render);
答案 3 :(得分:0)
报告您的进度,因此您的代码将如下所示:
public partial class MainWindow : Window
{
private BackgroundWorker worker;
public MainWindow()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += worker_ProgressChanged;
worker.WorkerReportsProgress = true;
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
Label lbl = new Label();
lbl.Content = e.ProgressPercentage;
stackPanel1.Children.Add(lbl);
}), DispatcherPriority.Background);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.IsEnabled = true;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 10; i++)
{
worker.ReportProgress(DataSupplier.GenerateRandomInt());
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
worker.RunWorkerAsync();
}
private int GenerateRandomInt(int i)
{
Thread.Sleep(1000);
return i;
}
}
class DataSupplier
{
public static int GenerateRandomInt()
{
Random rnd = new Random();
Thread.Sleep(1000);
return rnd.Next();
}
}