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

时间:2010-03-02 13:34:34

标签: c# wpf wcf xaml image-processing

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

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

2 个答案:

答案 0 :(得分:7)

在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<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;
  }
}

答案 1 :(得分:3)

扩展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<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;