C# from lagging while resizing

Viewed 185

I'm using a background worker to run a loop refreshing a PictureBox.

While the backgroundworker is running, I experience the following 2 issues:

  1. Delays when resizing the form to the point where the form could get hung for a few seconds.
  2. Trouble finding points on the form's border from where it can be resized, for example when hovering on the form's border, it could take a few seconds for the cursor to update and indicate the form is sizeable, and sometimes the cursor wont update at all.

Things I tried:

  • Invoking SuspendLayout()/ResumeLayout() in the form resize begin/end event handlers, didn't help.
  • Enabling the 'DoubleBuffered' prop for the form, didn't help.
  • Using a timer instead of a backgroundworker, but the the visuals were considerably slower than those produced by a backgroundworker running withtout delays. In this video I show how a background worker without delays performs comapred to a timer with the Interval set to 1.
  • Adding Thread.Sleep(1) inside the backgroundworker after refreshing the PictureBox, and think it solved issues related form resize, but visuals are again much slower.

My question: Is there a way to fix the from resize issues without slowing down visuals?

Below is a minimalistic code example recreating the issues I'm having:

using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace BGWDrawingTests
{
    public partial class Form1 : Form
    {
        BackgroundWorker bgw;
        bool someCondition = true;

        public Form1()
        {
            InitializeComponent();
            StartBGW_DoWork();
        }

        void StartBGW_DoWork()
        {
            // Start bgw and assign it the work 'BGW_DoWork'
            bgw = new BackgroundWorker();
            bgw.DoWork += new DoWorkEventHandler(BGW_DoWork);
            bgw.RunWorkerAsync();
        }

        void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
            // Keep drawing stuff while some condition is met
            while (someCondition)
            {
                RefreshPicBox();
                // Adding the delay seems to fix the form resize bug,
                // but also drastically slows drawing.
                //Thread.Sleep(1);
            }
        }

        void picBox_Paint(object sender, PaintEventArgs e)
        {
            // Draw stuff
        }

        delegate void RefreshPicBoxCallback();
        void RefreshPicBox()
        {
            // Thread safe "picBox.Refresh()"
            if (picBox.InvokeRequired)
                picBox.Invoke(new RefreshPicBoxCallback(RefreshPicBox), new object[] { });
            else picBox.Refresh();
        }
    }
}

For a bit more context, I'll mention that the backgroundworker is used to run a force directed graph drawing algorithm. The backgroundworker's actual DoWork method:

private void bgw_GraphViz(object sender, DoWorkEventArgs e)
{
    while (!formClosePending)
    {
        if (!formIsMinimized() && !graph.IsEmpty())
        {
            if (forcesEnabled) graph.ApplyForcesAndUpdatePositions();
            RefreshCanvas(); // Forces a redraw of the entire graph, the canvas is the PictureBox
            Thread.Sleep(1); // BUG - Delay needed to avoid form resize issues.
        }
    }
}

I noticed that when forces are enabled its more difficult to reproduce the bug, and I think its becuase applying forces slows down the drawing frequency, basically exactly what Thread.Sleep(1) achieves?

In case it helps, here's the code of the Form including the method bgw_GraphViz

EDIT:

I added a new timer with the Interval set to 1 from where I Invalidate the PictureBox, instead of forcing redraws in the backgroundworker by RefreshCanvas().

When I designed the graph drawing method, I thought it would be best to run a loop applying forces(moving nodes) by ApplyForcesAndUpdatePositions(), and then forcing a synchronous drawing of the graph. The problem with this approach is that the rate at which forces are applied depends on the speed of drawing the graph(FPS).

Now because drawings of the graph are no longer made synchronously on the same thread, The rate at which ApplyForcesAndUpdatePositions() is invoked now only depends on the size of the graph and does not depend on the FPS, and thus nodes move extremley fast when the graph is small. In this video I first show how the drawing of the graph used to be, that is, when drawings of the graph are synchronous, and then show how it would work if drawings of the graphs were async. Note that in this video I only used a backgroundworker, but it does not reflect the current code I use.

Some of the things I tried:

  • Adding delays after applying forces by ApplyForcesAndUpdatePositions(), This basically limits the calls to ApplyForcesAndUpdatePositions() at ~60 calls per sec, and the result was slow.
  • Counting Paint events of the graph to limit calls to ApplyForcesAndUpdatePositions(), again could do no better than a hard limit of ~60 calls per sec.
  • Binary semaphore/Lock/ManualResetEvent to sync the backgroundworker and the Paint event, again either hit a 60 call per sec hard limit or failed altogether.

I also considered using a StopWatch, but since Thread.Sleep(1) or Task.Delay(1) dont actually sleep for 1 millisec, and because I don't know of a way to "sleep" for less than 1 millisec, am currently unsure if the StopWatch is even applicable.

Honestly, I'm lost, I'm unsure how could I solve this problem.

0 Answers
Related