WPF API可以安全地用在WCF服务中吗?

我需要使用客户端XAML(来自Silverlight)并创建与服务器端资源(高分辨率图像)合并的位图,并且可以使用WPF(DrawingContext等)轻松地完成此操作。 有人提到服务器端(在IIS WCF中托管)使用WPF类似于在服务器上运行Office,这是一个非常糟糕的主意。

WPF是否可以在服务器上运行? 有哪些替代方案(尤其是xaml)? 我需要注意什么(内存泄漏,线程等)?

在WCF后面使用WPF服务器端并不等同于运行Office服务器端! WPF作为一个整体只是一些DLL,并且与使用任何其他库服务器端没什么不同。 这与Word或Excel 完全不同,后者在后台加载整个应用程序,包括用户界面,加载项,脚本语言等。

我多年来一直在WCF后面的服务器上使用WPF。 这是一个非常优雅和有效的解决方案:

  • 使用DirectX软件渲染是因为您没有使用实际的显示设备,但DirectX中的软件渲染例程已经过高度优化,因此您的性能和资源消耗将与您可能找到的任何渲染解决方案一样好,并且可能很多更好。

  • WPF的表现力允许使用优化的DirectX代码创建复杂的图形,而不是手工完成。

实际上,在WCF服务中使用WPF会为RAM占用空间增加大约10MB。

运行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 _workQueue = new Queue(); 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(Func func) { T result = default(T); InvokeOnPoolThread(delegate { result = func(); }); return result; } } 

扩展rayburns在这里说的是我如何使用STAthread,WPF和Asp.net WebApi。 我使用了parallel的扩展,特别是下面这个文件。

 //-------------------------------------------------------------------------- // // 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 StartNew(this TaskFactory factory, Func action, TaskScheduler scheduler) { return factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, scheduler); } public static Task StartNewSta(this TaskFactory factory, Func action) { return factory.StartNew(action, sharedScheduler); } private static TaskScheduler sharedScheduler = new StaTaskScheduler(1); } /// Provides a scheduler that uses STA threads. public sealed class StaTaskScheduler : TaskScheduler, IDisposable { /// Stores the queued tasks to be executed by our pool of STA threads. private BlockingCollection _tasks; /// The STA threads used by the scheduler. private readonly List _threads; /// Initializes a new instance of the StaTaskScheduler class with the specified concurrency level. /// The number of threads that should be created and used by this scheduler. public StaTaskScheduler(int numberOfThreads) { // Validate arguments if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel"); // Initialize the tasks collection _tasks = new BlockingCollection(); // 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()); } /// Queues a Task to be executed by this scheduler. /// The task to be executed. protected override void QueueTask(Task task) { // Push it into the blocking collection of tasks _tasks.Add(task); } /// Provides a list of the scheduled tasks for the debugger to consume. /// An enumerable of all tasks currently scheduled. protected override IEnumerable GetScheduledTasks() { // Serialize the contents of the blocking collection of tasks for the debugger return _tasks.ToArray(); } /// Determines whether a Task may be inlined. /// The task to be executed. /// Whether the task was previously queued. /// true if the task was successfully inlined; otherwise, false. 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); } /// Gets the maximum concurrency level supported by this scheduler. public override int MaximumConcurrencyLevel { get { return _threads.Count; } } ///  /// Cleans up the scheduler by indicating that no more tasks will be queued. /// This method blocks until all threads successfully shutdown. ///  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; } } } } 

用法非常简单。 只需使用以下代码即可使用扩展程序

  Task 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;