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

Posted

技术标签:

【中文标题】WPF API 可以安全地用于 WCF 服务吗?【英文标题】:Can the WPF API be safely used in a WCF service? 【发布时间】:2011-01-22 17:35:33 【问题描述】:

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

WPF 是为在服务器上运行而构建的吗?有哪些替代方案(尤其是 xaml)?我需要注意什么(内存泄漏、线程等)?

【问题讨论】:

这个问题是真实的,不应该因为主观和争论而被关闭。 【参考方案1】:

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

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

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

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

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

我在运行 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;
  

【讨论】:

如果 WPF 应用程序运行在具有高端显卡或更好的服务器上,其中一些,有没有办法强制它使用特定的卡进行渲染? 我不知道有什么方法可以强制将成像计算卸载到 GPU 上。当服务器应用程序在实际连接到物理显示器的桌面上运行时,它会自动发生。它也可能在其他时间自动发生。 @RayBurns - 我正在尝试使用 WPF 在 Http 处理程序中绘制 jpeg,有点像水印类型的东西。我收到 STA 错误,但我不太确定如何使用您的解决方案来修复它。如果我在一个单独的线程上工作,我的 Http Handler 不会在它完成之前返回吗?【参考方案2】:

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

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

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

    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;

【讨论】:

以上是关于WPF API 可以安全地用于 WCF 服务吗?的主要内容,如果未能解决你的问题,请参考以下文章

哪位大侠以简单易懂的语言给我介绍下 WCF和WPF 分别是干啥用的?

Web API 和 WCF 的比较

对 WCF 的 WPF 服务调用无法访问已释放的对象

我如何在 wpf 应用程序中使用 wcf [关闭]

WCF的替代品[关闭]

在哪里存储当前 WCF 调用的数据? ThreadStatic 安全吗?