Home > Mobile >  UI is being blocked by Performance counters
UI is being blocked by Performance counters

Time:08-18

so I have a button, a panel and a usercontrol, on a click of the button the usercontrol is being called and shown on the panel, the problem is that it takes around 4-5 seconds for the usercontrol to show due to performance counters.

the usercontrol contains a timer, the timer simply updates the two labels (to show cpu and ram usage)

private void UpdateUI_Tick(object sender, EventArgs e)
{
    Task.Run(() => {
        LabelCPU.Text = $"CPU : {(int)PerformanceCounterCPU.NextValue()}%";
        LabelMemory.Text = $"Memory : {(int)PerformanceCounterMemory.NextValue()}%";
    });
}

I tried everything but it just keeps on freezing.

the usercontrol contains this code:

private static UserControlLogs _instance;
public static UserControlLogs Instance
{
    get
    {
        if (_instance == null || _instance.IsDisposed)
            _instance = new UserControlLogs();
        return _instance;
    }
}

the code of the button that calls the usercontrol:

if (!PanelMain.Controls.Contains(UserControls.UserControlLogs.Instance))
{
    PanelMain.Controls.Add(UserControls.UserControlLogs.Instance);
    uc.Dock = DockStyle.Fill;
    uc.BringToFront();
}
else
{
    uc.BringToFront();
}

CodePudding user response:

Based on our conversation, even after applying various strategies you are still experiencing system freezes. The purpose of re-enabling this answer is to show all the things that have been tried so far. This way, if anyone else wants to take a crack at it they'll be able to cover new ground.


  1. Override the OnVisibleChanged of the UserControl to initialize. Start a Task to create PerformanceCounters on the first occurrence of Visible (spin a WaitCursor while it's taking place) and then start the timer.

public partial class UserControlLogs : UserControl
{
    protected override void OnVisibleChanged(EventArgs e)
    {
        base.OnVisibleChanged(e);
        if(Visible)
        {
            if(PerformanceCounterCPU == null)
            {
                Task.Run(()=>
                {
                    Parent?.Invoke((MethodInvoker)delegate { Parent.UseWaitCursor = true; });
                    Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Initializing Performance Counters");
                    UserControlLogs.Stopwatch.Restart();
                    PerformanceCounterCPU = new PerformanceCounter("Processor", "% Processor Time", "_Total");
                    PerformanceCounterCommittedMemory = new PerformanceCounter("Memory", "% Committed Bytes In Use");
                    PerformanceCounterAvailableMemory = new PerformanceCounter("Memory", "Available MBytes");
                    Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Initialization complete");
                    UserControlLogs.Stopwatch.Restart();
                    Parent?.BeginInvoke((MethodInvoker)delegate 
                    {
                        Parent.UseWaitCursor = false;
                        timerUpdateUI.Enabled = true;
                    });
                });
            }
        }
    }
}

  1. Add a semaphore to block reentrancy. If the Task hasn't had a chance to complete, this will avoid starting new ones and having them pile up.

  2. Make the acquisition of data separate from the updating of the UI. The PerformanceCounters can be read on a background thread. Then just take the result and marshal it back onto the UI thread to display.

  3. Add Stopwatch to diagnose the exact lines that are causing bottlenecks. Output the Elapsed intervals to the Debug window to monitor.


public partial class UserControlLogs : UserControl
{
    public static Stopwatch Stopwatch { get; } = new Stopwatch();
    public UserControlLogs()
    {
        InitializeComponent();
        timerUpdateUI.Enabled = false;
    }
    private PerformanceCounter PerformanceCounterCPU = null;
    private PerformanceCounter PerformanceCounterCommittedMemory = null;
    private PerformanceCounter PerformanceCounterAvailableMemory = null;

    SemaphoreSlim _noReentrancy = new SemaphoreSlim(1, 1);

    // Handler is now async
    private async void timerUpdateUI_Tick(object sender, EventArgs e)
    {
        int cpu = 0, committed = 0, available = 0;
        
        // Skip if task is still busy getting the Performance Counter from last time.
        if (_noReentrancy.Wait(0))
        {
            var cts = new CancellationTokenSource();
            var wdt = startWatchdog(TimeSpan.FromSeconds(2));
            try
            {
                _ = Task.Run(() => getPerformance());
                await wdt; 
                BeginInvoke((MethodInvoker)delegate { updateUI(); });
            }
            catch (TimeoutException ex)
            {
                Debug.WriteLine($"{DateTime.Now} {ex.Message}");
            }
            finally
            {
                _noReentrancy.Release();
            }

            void getPerformance()
            {
                Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Getting performance data");
                UserControlLogs.Stopwatch.Restart();
                cpu = (int)PerformanceCounterCPU.NextValue();
                committed = (int)PerformanceCounterCommittedMemory.NextValue();
                available = (int)PerformanceCounterAvailableMemory.NextValue();
                cts.Cancel(); // Cancel the WDT
                Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Performance data received");
                UserControlLogs.Stopwatch.Restart();
            }
            void updateUI()
            {
                Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: UI Updating");
                UserControlLogs.Stopwatch.Restart();
                labelCPU.Text = $"{cpu}%";
                labelMemoryCommitted.Text = $"{committed}%";
                labelMemoryAvailable.Text = $"{available}%";

                _colorToggle = !_colorToggle;
                if(_colorToggle) BackColor = Color.LightCyan;
                else BackColor = Color.LightBlue;
                Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: UI Updated");
                UserControlLogs.Stopwatch.Restart();
            }
            async Task startWatchdog(TimeSpan timeout)
            {
                try
                {
                    Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Watchdog Started");
                    UserControlLogs.Stopwatch.Restart();
                    await Task.Delay(timeout, cts.Token);
                    Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Watchdog Timed Out");
                    UserControlLogs.Stopwatch.Restart();
                    throw new TimeoutException();
                }
                catch (TaskCanceledException) 
                {
                    Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: Watchdog Cancelled");
                    UserControlLogs.Stopwatch.Restart();
                }
            }
        }
    }
}

  1. Don't block the button click. Use a BeginInvoke to show the UserControl.

public partial class MainForm : Form
{
    public MainForm()
    {
        UserControlLogs.Stopwatch.Start();
        Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: MainForm CTor:");
        InitializeComponent();
    }

    private void buttonShowUC_Click(object sender, EventArgs e)
    {
        BeginInvoke((MethodInvoker)delegate
        {
            Debug.WriteLine($"{UserControlLogs.Stopwatch.Elapsed}: ShowUC Click:");
            UserControlLogs.Stopwatch.Restart();
            _instance.Show();
        });
    }
}
  • Related