.NET Framework - multithreading with WPF

Asked By hbrowe on 14-Mar-08 06:50 PM
I'm trying to understand how to speed up the ui.  I have been experimenting
with background processing, but am not getting results that I think I should.

To experiment, I've created a window that contains a bunch of randomly sized
and placed circles.  My goal is to be able to resize the window without it
hanging or being sluggish.  To reduce the processing in the main thread, I've
created a BackgroundWorker thread to handle as much processing as I can.
Given the design, I believe that the windows sizing shouldn't be affected by
the value of numberOfPoints.  I expect the update to be delayed, but the
resizing to be independent; however, that is not what I am getting.


Does anyone have any suggestions?
Also, I found that I was not able to freeze a VisualBrush after setting the
visual.  Any suggestions on that?



Here's my sample code:


using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace FreezeTesting
{
class FreezeTesting
{
}

class DrawingPanel : Canvas
{
class WorkData
{
public Size Size { get; private set; }
public WorkData(Size s)
{
Size = s;
}
}
static Random r = new Random();
const int numberOfPoints = 1000;
bool vis = false;
BackgroundWorker bgworker = new BackgroundWorker();
public DrawingPanel()
{
Background = Brushes.White;
bgworker.WorkerSupportsCancellation = true;
bgworker.WorkerReportsProgress = true;
bgworker.DoWork += new DoWorkEventHandler(bgworker_DoWork);
bgworker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bgworker_RunWorkerCompleted);
}

void bgworker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == false)
{
VisualBrush vb = e.Result as VisualBrush;
if (vb != null)
{
Background = vb;
}
DrawingBrush db = e.Result as DrawingBrush;
if (db != null)
{
Background = db;
}
}
}

void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = null;
WorkData wd = e.Argument as WorkData;
if (wd != null)
{
if (vis)
{
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
for (int i = 0; i < numberOfPoints; i++)
{
int rad = r.Next(10) + 10;
dc.DrawEllipse(Brushes.Black, null, new
Point(r.NextDouble() * wd.Size.Width, r.NextDouble() * wd.Size.Height), rad,
rad);
if (e.Cancel == true)
return;
}
}
VisualBrush vb = new VisualBrush(dv);
vb.Freeze(); // throws an error... don't understand why
e.Result = vb;
}
else
{
GeometryGroup gg = new GeometryGroup();
for (int i = 0; i < numberOfPoints; i++)
{
int rad = r.Next(10) + 10;
gg.Children.Add(new EllipseGeometry(new
Point(r.NextDouble() * wd.Size.Width, r.NextDouble() * wd.Size.Height), rad,
rad));
if (e.Cancel == true)
return;
}
GeometryDrawing gd = new GeometryDrawing(null, new
Pen(Brushes.Black, 1), gg);
DrawingBrush db = new DrawingBrush();
db.Drawing = gd;
db.Freeze();
e.Result = db;
}
}
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (bgworker.IsBusy)
bgworker.CancelAsync();
bgworker.RunWorkerAsync(new WorkData(sizeInfo.NewSize));
}
}

class Win : Window
{
public Win()
{
Content = new DrawingPanel();
}
}

class App : Application
{
[STAThread()]
static void Main()
{
new App();
}
App()
{
Run(new Win());
}
}
}




Jon Skeet [C# MVP] replied on 14-Mar-08 07:04 PM
It looks like you're trying to do UI work from a non-UI thread. That's
just not the done thing. UI work *has* to be done on a UI thread - and
it sounds like pretty much all of your code is doing UI work, so I'm
not sure you'll be able to improve it using threading.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
hbrowe replied on 15-Mar-08 02:46 PM
I believe the purpose of the freezable class is to enable sharing data
accross treads.  From the second paragraph of the freezable objects overview
(ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/wpf_conceptual/html/89c71692-4f43-4057-b611-67c6a8a863a2.htm):

threads, while an unfrozen Freezable cannot."

Freezing an object, as I understand it, would be done prior to passing the
data back to a ui thread.  From my example code, if you commet out the line
'db.Freeze()" in the function bgworker_DoWork, the program will assert on
trying to set "Background = db;", which agrees with my interpretation of the
documentation.  Though, since the window's responsiveness is proprotional to
how many circles created in the backgroundworker thread, I assume that that
thread is executed synchronously.  Is there something else that needs to be
specified to override that behavior?

To introduce another angle on this discussion, what I would like is
something that essentially behaves like animation where I am merely swapping
background images on an as needed basis.  If a few frames are skipped, I
don't really care, but I want the update to be fast.  Is there a way to map
drawings into an image buffer that is simply drawn to a window?  Can I make
the DrawingBrush a bitmap image (in the background worker) or something else
that can be drawn to the screen very quickly when it is passed back to the ui
thread?
Jon Skeet [C# MVP] replied on 15-Mar-08 03:14 PM
Okay, that makes sense - I readily admit I'm far from an expert on WPF.


Well, it may not be executed synchronously - it may just be hogging the
processor. Do you actually have two cores? If not, it's not going to
speed things up by offloading it onto a different thread.


I'm afraid at that point you're well beyond my knowledge of WPF.

One thing you asked before, however, is why you couldn't freeze your
VisualBrush. This is in the documentation:

A VisualBrush cannot be made read-only (frozen) when its Visual
property is set to any value other than null.

Admittedly that's just confirmation rather than a real *reason*, but I
suspect it's that it can't freeze the contents of the Visual.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet   Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
hbrowe replied on 16-Mar-08 10:29 AM
The solution is much much easier than I realized. I simply missed the
documentation in msdn on WPF threading.  For those of you looking for answers
to threading in WPF, there is an excellent chapter in MSDN:

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/wpf_conceptual/html/02d8fd00-8d7c-4604-874c-58e40786770b.htm