2

I have created a demo, that will hopefully demonstrate the problem I have been having for the last couple of days.

I have created a simple class named Data Supplier with a single public static method named GenerateRandomInt() this is to simulate having a process which might take some time. The code for the aforementioned class can be found below:

class DataSupplier
    {
        public static int GenerateRandomInt()
        {
            Random rnd = new Random();
            Thread.Sleep(1000);
            return rnd.Next();
        }
    }

My MainWindow simple contains a ScrollViewer with StackPanel embedded inside it named stackPanel1 , also a Button named button1 the XAML for this is shown below:

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

What I want to achieve, is when I click button1 10 labels are generated, which display a random number using the static DataSupplier.GetRandomInt() method. However I want them to be displayed one by one, i.e. as soon as they are individually created. The codebehind for my MainWindow is shown below:

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

However, what is happening is that nothing visually appears until all 10 of my randomly generated labels have been added to stackPanel1.

Is there anyway I can program this so that each label appears as each one is created? Whilst also ensuring that my UI remains responsive?

4

4 回答 4

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

这会给你相同的结果并且更简单(只是我的观点)

于 2013-09-10T09:48:36.257 回答
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 总是很忙。

于 2013-09-10T08:36:43.510 回答
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);
于 2013-09-10T08:39:46.610 回答
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();
    }
}
于 2013-09-10T10:05:36.750 回答