Можно ли безопасно использовать API WPF в службе WCF? - PullRequest
13 голосов
/ 02 марта 2010

У меня есть требование взять XAML на стороне клиента (из Silverlight) и создать растровое изображение, объединенное с ресурсом на стороне сервера (изображение с высоким разрешением), и могу сделать это довольно легко, используя WPF (DrawingContext и т. Д.). Было упомянуто, что использование WPF на стороне сервера (размещено в IIS WCF) сродни запуску Office на сервере и является действительно плохой идеей.

WPF создан для работы на сервере? Какие есть альтернативы (особенно с xaml)? На что мне нужно обращать внимание (утечки памяти, многопоточность и т. Д.)?

Ответы [ 2 ]

7 голосов
/ 03 марта 2010

Использование WPF на стороне сервера за WCF не эквивалентно работе на стороне сервера Office! WPF в целом - это всего лишь несколько DLL, и он ничем не отличается от использования любой другой библиотеки на стороне сервера. Это полностью отличается от Word или Excel, где вы загружаете приложение целиком, включая пользовательские интерфейсы, надстройки, язык сценариев и т. Д.

Я использую WPF на сервере за WCF в течение многих лет. Это очень элегантное и эффективное решение:

  • Программный рендеринг DirectX используется потому, что вы не рисуете на реальном устройстве отображения, но процедуры рендеринга программного обеспечения в DirectX были высоко оптимизированы, поэтому ваша производительность и потребление ресурсов будут такими же хорошими, как и у любого решения для рендеринга, которое вы используете. может найти, и, вероятно, гораздо лучше.

  • Выразительность WPF позволяет создавать сложные графики с использованием оптимизированного кода DirectX, а не делать это вручную.

На практике использование WPF из службы WCF увеличит объем используемой оперативной памяти примерно на 10 МБ.

У меня не было проблем с утечкой памяти при работе на стороне сервера 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;
  }
}
3 голосов
/ 17 сентября 2012

Вкратце о том, что сказал 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...