Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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). 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.

Is WPF built to run on the server? What are the alternatives (particularly with xaml)? What do I need to look out for (memory leaks, threading etc)?

like image 485
Simon Munro Avatar asked Mar 02 '10 13:03

Simon Munro


People also ask

Can WPF be targeted to Web Browser Yes or no?

WPF only runs on windows. You can make a type of wpf application called xbap which runs in a browser. BUT. Only on windows.

What is WPF and WCF in .NET explain with an example?

WCF = Windows COMMUNICATION Foundation. WPF = Windows PRESENTATION Foundation. WCF deals with communication (in simple terms - sending and receiving data as well as formatting and serialization involved), WPF deals with presentation (UI) Follow this answer to receive notifications.

Is WPF frontend or backend?

WPF uses XAML as its frontend language and C# as its backend languages. WPF was introduced as a part of . NET Framework 3.0 as the Windows library to build Windows client apps and the next generation of Windows Forms. The current version of WPF is 4.5.

What is WPF Web service?

WPF is part of . NET, so if you have previously built applications with . NET using ASP.NET or Windows Forms, the programming experience should be familiar. WPF uses the Extensible Application Markup Language (XAML) to provide a declarative model for application programming.


2 Answers

Using WPF server-side behind WCF is not equivalent to running Office server-side! WPF as a whole is just a few DLLs, and is really no different than using any other library server-side. 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.

I have been using WPF on the server behind WCF for years. 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.

  • WPF's expressivity allows complex graphics to be created using optimized DirectX code rather than doing it by hand.

Practically speaking, using WPF from within your WCF service will add about 10MB to your RAM footprint.

I have not had any memory leak problems with running WPF server-side. 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. 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.

One threading issue you will encounter is that WPF requires STA threads and WCF uses MTA threads. 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. I wrote a little STAThreadPool class that handles the transition. 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;
  }
}
like image 166
Ray Burns Avatar answered Oct 12 '22 19:10

Ray Burns


Expanding on what rayburns said here is how I'm using STAthread, WPF and Asp.net WebApi. I used the parallel's extensions, specifically this file below.

//--------------------------------------------------------------------------
// 
//  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;
like image 39
Particleman Avatar answered Oct 12 '22 19:10

Particleman