简体   繁体   English

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

[英]Can the WPF API be safely used in a WCF service?

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

Is WPF built to run on the server? WPF是否可以在服务器上运行? What are the alternatives (particularly with xaml)? 有哪些替代方案(特别是xaml)? What do I need to look out for (memory leaks, threading etc)? 我需要注意什么(内存泄漏,线程等)?

Using WPF server-side behind WCF is not equivalent to running Office server-side! 在WCF后面使用WPF服务器端并不等同于运行Office服务器端! WPF as a whole is just a few DLLs, and is really no different than using any other library server-side. WPF作为一个整体只是一些DLL,并且与使用任何其他库服务器端没有什么不同。 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. 这与Word或Excel 完全不同,后者在后台加载整个应用程序,包括用户界面,加载项,脚本语言等。

I have been using WPF on the server behind WCF for years. 我多年来一直在WCF背后的服务器上使用WPF。 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. 使用DirectX软件渲染是因为您没有使用实际的显示设备,但DirectX中的软件渲染例程已经过高度优化,因此您的性能和资源消耗将与您可能找到的任何渲染解决方案一样好,并且可能很多更好。

  • WPF's expressivity allows complex graphics to be created using optimized DirectX code rather than doing it by hand. WPF的表现力允许使用优化的DirectX代码创建复杂的图形,而不是手工完成。

Practically speaking, using WPF from within your WCF service will add about 10MB to your RAM footprint. 实际上,在WCF服务中使用WPF会为RAM占用空间增加大约10MB。

I have not had any memory leak problems with running WPF server-side. 运行WPF服务器端时没有任何内存泄漏问题。 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. 我也在使用XamlReader将XAML解析为对象树,并且发现当我停止引用对象树时,垃圾收集器会收集它没有问题。 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. 我总是认为如果我在WPF中遇到内存泄漏,我会通过在一个单独的AppDomain中运行来解决它,你偶尔会回收它,但我从来没有真正遇到过。

One threading issue you will encounter is that WPF requires STA threads and WCF uses MTA threads. 您将遇到的一个线程问题是WPF需要STA线程而WCF使用MTA线程。 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. 这不是一个重要的问题,因为您可以拥有一个STA线程池来获得与MTA线程相同的性能。 I wrote a little STAThreadPool class that handles the transition. 我写了一个处理转换的STAThreadPool类。 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. 扩展rayburns在这里说的是我如何使用STAthread,WPF和Asp.net WebApi。 I used the parallel's extensions, specifically this file below. 我使用了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;
            }
        }
    }
}

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM