I have a requirement to take client side XAML (from Silverlight) and create a bitmap merged with a server side resource (high res image) and can do this quite easily using WPF (DrawingContext etc). It has been mentioned that server side (hosted in IIS WCF) use of WPF is akin to running Office on the server and a really bad idea.
Is WPF built to run on the server? What are the alternatives (particularly with xaml)? What do I need to look out for (memory leaks, threading etc)?
WPF only runs on windows. You can make a type of wpf application called xbap which runs in a browser. BUT. Only on windows.
WCF = Windows COMMUNICATION Foundation. WPF = Windows PRESENTATION Foundation. WCF deals with communication (in simple terms - sending and receiving data as well as formatting and serialization involved), WPF deals with presentation (UI) Follow this answer to receive notifications.
WPF uses XAML as its frontend language and C# as its backend languages. WPF was introduced as a part of . NET Framework 3.0 as the Windows library to build Windows client apps and the next generation of Windows Forms. The current version of WPF is 4.5.
WPF is part of . NET, so if you have previously built applications with . NET using ASP.NET or Windows Forms, the programming experience should be familiar. WPF uses the Extensible Application Markup Language (XAML) to provide a declarative model for application programming.
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;
}
}
Expanding on what rayburns said here is how I'm using STAthread, WPF and Asp.net WebApi. I used the parallel's extensions, specifically this file below.
//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: StaTaskScheduler.cs
//
//--------------------------------------------------------------------------
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace System.Threading.Tasks.Schedulers
{
public static class ParallelExtensions
{
public static Task StartNew(this TaskFactory factory, Action action, TaskScheduler scheduler)
{
return factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
public static Task<TResult> StartNew<TResult>(this TaskFactory factory, Func<TResult> action, TaskScheduler scheduler)
{
return factory.StartNew<TResult>(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
public static Task<TResult> StartNewSta<TResult>(this TaskFactory factory, Func<TResult> action)
{
return factory.StartNew<TResult>(action, sharedScheduler);
}
private static TaskScheduler sharedScheduler = new StaTaskScheduler(1);
}
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
}
Usage is pretty easy. Just use the code below to use the extension
Task<MemoryStream> Task1 = Task.Factory.StartNewSta(() =>
{
/* use wpf here*/
BitmapEncoder PngEncoder =
new PngBitmapEncoder();
PngEncoder.Frames.Add(BitmapFrame.Create(Render));
//save to memory stream
var Ms = new MemoryStream();
PngEncoder.Save(Ms);
return Ms;
});
Task.WaitAll(Task1);
return Task1.Result;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With