Использование WPF на стороне сервера за WCF не эквивалентно работе на стороне сервера Office! WPF в целом - это всего лишь несколько DLL, и он ничем не отличается от использования любой другой библиотеки на стороне сервера. Это полностью отличается от Word или Excel, где вы загружаете приложение целиком, включая пользовательские интерфейсы, надстройки, язык сценариев и т. Д.
Я использую WPF на сервере за WCF в течение многих лет. Это очень элегантное и эффективное решение:
Программный рендеринг DirectX используется потому, что вы не рисуете на реальном устройстве отображения, но процедуры рендеринга программного обеспечения в DirectX были высоко оптимизированы, поэтому ваша производительность и потребление ресурсов будут такими же хорошими, как и у любого решения для рендеринга, которое вы используете. может найти, и, вероятно, гораздо лучше.
Выразительность WPF позволяет создавать сложные графики с использованием оптимизированного кода DirectX, а не делать это вручную.
На практике использование WPF из службы WCF увеличит объем используемой оперативной памяти примерно на 10 МБ.
У меня не было проблем с утечкой памяти при работе на стороне сервера WPF. Я также использую XamlReader для анализа XAML в деревьях объектов и обнаружил, что когда я прекращаю ссылаться на дерево объектов, сборщик мусора собирает его без проблем. Я всегда полагал, что если бы я столкнулся с утечкой памяти в WPF, я бы обошел ее, запустив отдельный домен AppDomain, который вы время от времени перезагружали, но на самом деле я никогда не сталкивался с ним.
Одна проблема с потоками, с которой вы столкнетесь, заключается в том, что WPF требует потоков STA, а WCF использует потоки MTA. Это не является серьезной проблемой, поскольку вы можете иметь пул потоков STA, чтобы получить такую же производительность, как и у потоков MTA. Я написал небольшой класс STAThreadPool, который обрабатывает переход. Вот оно:
// 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;
}
}