2010-03-02 91 views
13

我有一個需要採取客戶端XAML(從Silverlight),並創建一個位圖與服務器端資源合併(高分辨率圖像),並可以很容易地使用WPF(DrawingContext等)來做到這一點。已經提到服務器端(在IIS WCF中託管)使用WPF類似於在服務器上運行Office,這是一個非常糟糕的主意。WPF API可以在WCF服務中安全使用嗎?

是否構建WPF以在服務器上運行?有什麼選擇(特別是XAML)?我需要注意什麼(內存泄漏,線程等)?

+3

最多可以有4個。 – 2010-03-02 13:37:16

+1

這個問題是真實的,不應該因爲主觀和議論而關閉。 – 2010-03-02 13:37:58

回答

7

使用WPF服務器端WCF後面是而不是等同於運行Office服務器端!作爲一個整體,WPF只是一些DLL,並且與使用任何其他庫服務器端沒有什麼不同。這是完全不同於Word或Excel,您在後臺加載整個應用程序,包括用戶界面,加載項,腳本語言等。

我一直在WCF服務器後面使用WPF for年份。這是一個非常優雅和高效的解決方案:

  • 的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; 
    } 
} 
+0

如果WPF應用程序在高端圖形卡的服務器上運行,或者更好,那麼其中的一些有什麼方法可以強制它使用特定的卡進行渲染? – 2010-03-04 21:50:26

+0

我不知道有什麼方法強制將成像計算卸載到GPU上。它在服務器應用程序在實際連接到物理顯示器的桌面上運行時自動發生。它也可能在其他時間自動發生。 – 2010-03-05 07:15:03

+0

@RayBurns - 我試圖用WPF在Http Handler中繪製一個jpeg,有點像水印類型的東西。我收到STA錯誤,但我不太清楚如何使用您的解決方案來修復它。如果我在單獨的線程上完成我的工作,那麼在它完成之前,我的Http處理程序不會返回嗎? – xr280xr 2012-09-17 16:14:44

3

擴展什麼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;