长时间运行的任务阻止了UI

时间:2016-02-18 20:37:33

标签: c# multithreading task-parallel-library

我是TPL的新手,我正在尝试使用并行性测试一个非常简单的应用程序。

我正在使用WinForms,C#,VS2015

在我的表格中,我有一个进度条,一个计时器和一个计量器。 我正在使用 Infragistics 15.2 控件。

  • 按钮将启动一个功能,可以完成一些工作
  • 表单加载将启动Windows.Form.Timer并实例化PerformanceCounter
  • timer.Tick(我设置500ms间隔),我从PerformanceCounter读取CPU使用情况并更新Gauge控件的值。

我的问题是第一次访问CPU计数器的NextValue()实际上是非常昂贵的,并冻结了UI。 我期待有一个完整的响应式用户界面,但它仍然冻结。我肯定错过了一些东西,但我找不到什么。 我非常确定阻止操作是NextValue():如果我用随机数生成器替换它,我的UI就会完全响应。

你能帮帮我吗?

public partial class Form1 : Form
{
    PerformanceCounter _CPUCounter;
    public Form1()
    {
        InitializeComponent();

    }

    private async void ultraButton1_Click(object sender, EventArgs e)
    {
        var progress = new Progress<int>(valuePBar => {ultraProgressBar1.Value = valuePBar; });
        await Task.Run(() => UpdatePBar(progress));
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Task.Run(() =>
        {
            _CPUCounter = new PerformanceCounter();
            _CPUCounter.CategoryName = "Processor";
            _CPUCounter.CounterName = "% Processor Time";
            _CPUCounter.InstanceName = "_Total";
            BeginInvoke(new Action(() =>
            {
                timer1.Interval = 500;
                timer1.Start();
            }));
        });
    }


    private async void timer1_Tick(object sender, EventArgs e)
    {
        float usage = await Task.Run(() => _CPUCounter.NextValue());
        RadialGauge r_gauge = (RadialGauge)ultraGauge1.Gauges[0];
        r_gauge.Scales[0].Markers[0].Value = usage;
    }

    public void UpdatePBar(IProgress<int> progress)
    {
        for (Int32 seconds = 1; seconds <= 10; seconds++)
        {
            Thread.Sleep(1000); //simulate Work, do something with the data received
            if (seconds != 10 && progress != null)
            {
                progress.Report(seconds * 10);
            }
        }
    }
}

4 个答案:

答案 0 :(得分:2)

你应该:

  • 永远不要从后台线程更新UI控件。
  • 首选Task.Run Task.Factory.StartNew
  • 首选async / await,如果您希望&#34;返回UI线程&#34;。
  • 使用IProgress<T>进行更新。
  • 如果绝对必要,仅使用TaskSchedulerSynchronizationContext s(此处不需要它们)。
  • 切勿使用Control.BeginInvokeControl.Invoke

在这种情况下,您的计时器可以使用NextValue在线程池线程上运行Task.Run,然后使用结果值更新UI:

private async void OnTimerTickElapsed(Object sender, EventArgs e)
{
  float usage = await Task.Run(() => _CPUCounter.NextValue());
  RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];                   
  r_gauge.Scales[0].Markers[0].Value = usage;
}

对于您的进度条,让MyMethod选择IProgress<int>

private void ButtonClick(Object sender, EventArgs e)
{
  var progress = new Progress<int>(valuePBar => { this.progressBar.Value = valuePBar; });
  MyMethod(progress);
}

MyMethod(IProgress<int> progress)
{
  ...
  progress.Report(50);
  ...
}

答案 1 :(得分:1)

三件事......

  1. 简化进度条的线程同步上下文。只需使用Task.Run(() => { // progress bar code }
  2. 即可
  3. 检查CPUCounter不是static
  4. 确保您的_CPUCounter.NextValue()方法支持异步并等待,否则会阻止。
  5. 我也赞成Task.Run超过Task.Factory.StartNew,因为它有更好的默认值(内部调用Task.Factory.StartNew

    private void OnTimerTickElapsed(Object sender, EventArgs e)
            {
                await Task.Run(async () =>
                {
                    float usage = await _CPUCounter.NextValue();           
                    RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];                   
                    r_gauge.Scales[0].Markers[0].Value = usage;
                }, TaskCreationOptions.LongRunning);
            }
    

    查看您正在使用的间隔。对于TPL的开销,50ms有点快。

答案 2 :(得分:1)

尝试了您的代码(只是用显示usage变量的简单标签替换了量表,当然没有点击按钮,因为您没有提供MyMethod)并且没有& #39;体验任何UI阻止。

发布用于更新UI的正确代码(即使使用UI调度程序IMO,使用Task也是一种过度杀伤):

private void UpdateProgressBar(Int32 valuePBar)
{
    BeginInvoke(new Action(() =>
    {
        this.progressBar.Value = valuePBar;
    }));
}

private void OnTimerTickElapsed(Object sender, EventArgs e)
{
    Task.Run(() =>
    {
        float usage = _CPUCounter.NextValue();
        BeginInvoke(new Action(() =>
        {
            RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];
            r_gauge.Scales[0].Markers[0].Value = usage;
        }));
    });
}

但是,代码中没有明显的原因会导致UI阻塞。 最后这部分除外

_CPUCounter = new PerformanceCounter();
_CPUCounter.CategoryName = "Processor";
_CPUCounter.CounterName = "% Processor Time";
_CPUCounter.InstanceName = "_Total";

OnFormLoad的UI线程上运行。您可以尝试在这样的单独任务上移动该代码

private void OnFormLoad(Object sender, EventArgs e)
{
    Task.Run(() =>
    {
        _CPUCounter = new PerformanceCounter();
        _CPUCounter.CategoryName = "Processor";
        _CPUCounter.CounterName = "% Processor Time";
        _CPUCounter.InstanceName = "_Total";
        BeginInvoke(new Action(() =>
        {
            this.timer.Interval = 500;
            this.timer.Start();
        }));
    });
}

答案 3 :(得分:1)

Somebody has already investigated this problem

问题是PerformanceCounter初始化很复杂并且需要花费很多时间。但不清楚的是,这个初始化是阻止所有线程还是只是拥有线程(创建PerformanceCounter的线程)。

但我认为您的代码已经提供了答案。由于您已经在线程池线程中创建了PerformanceCounter,它仍然会阻止UI。假设初始化阻塞所有线程可能是正确的。

如果初始化阻止所有线程,那么解决方案是在应用程序启动时初始化PerformanceCounter。通过不使用默认构造函数(选择不仅构造实例而且还初始化它的构造函数)或在启动期间调用NextValue一次。