Using WPF server-side behind WCF is not equivalent to running Office server-side! WPF as a whole is just a few DLLs, and is really no different than using any other library server-side. This is completely different from Word or Excel, where you are loading an entire application behind the scenes, including user interfaces, add-ins, scripting language, etc.
I have been using WPF on the server behind WCF for years. It is a very elegant and efficient solution:
DirectX software rendering is used because you are not drawing on an actual display device, but the software rendering routines in DirectX have been highly optimized so your performance and resource consumption is going to be as good as any rendering solution you might find, and probably much better.
WPF's expressivity allows complex graphics to be created using optimized DirectX code rather than doing it by hand.
Practically speaking, using WPF from within your WCF service will add about 10MB to your RAM footprint.
I have not had any memory leak problems with running WPF server-side. I am also using XamlReader to parse XAML into object trees and have found that when I stop referencing the object tree the garbage collector collects it with no problem. I always figured that if I did run into a memory leak in WPF I would work around it by running in a separate AppDomain which you would occasionally recycle, but I never actually encountered one.
One threading issue you will encounter is that WPF requires STA threads and WCF uses MTA threads. This is not a significant problem since you can have a pool of STA threads to get the same performance as you would from MTA threads. I wrote a little STAThreadPool class that handles the transition. Here it is:
// A simple thread pool implementation that provides STA threads instead of the MTA threads provided by the built-in thread pool
public class STAThreadPool
{
int _maxThreads;
int _startedThreads;
int _idleThreads;
Queue<Action> _workQueue = new Queue<Action>();
public STAThreadPool(int maxThreads)
{
_maxThreads = maxThreads;
}
void Run()
{
while(true)
try
{
Action action;
lock(_workQueue)
{
_idleThreads++;
while(_workQueue.Count==0)
Monitor.Wait(_workQueue);
action = _workQueue.Dequeue();
_idleThreads++;
}
action();
}
catch(Exception ex)
{
System.Diagnostics.Trace.Write("STAThreadPool thread threw exception " + ex);
}
}
public void QueueWork(Action action)
{
lock(_workQueue)
{
if(_startedThreads < _maxThreads && _idleThreads <= _workQueue.Count)
new Thread(Run) { ApartmentState = ApartmentState.STA, IsBackground = true, Name = "STAThreadPool#" + ++_startedThreads }.Start();
_workQueue.Enqueue(action);
Monitor.PulseAll(_workQueue);
}
}
public void InvokeOnPoolThread(Action action)
{
Exception exception = null;
using(ManualResetEvent doneEvent = new ManualResetEvent(false)) // someday: Recycle these events
{
QueueWork(delegate
{
try { action(); } catch(Exception ex) { exception = ex; }
doneEvent.Set();
});
doneEvent.WaitOne();
}
if(exception!=null)
throw exception;
}
public T InvokeOnPoolThread<T>(Func<T> func)
{
T result = default(T);
InvokeOnPoolThread(delegate
{
result = func();
});
return result;
}
}