2010年12月8日 星期三

Silverlight TreeView 的 DataBinding (1)

這一天研究了 Silverlight TreeView 的 DataBinding. 這裡作一下簡單的記錄,以便自己回憶。

首先,使用 Silverlight Application 建立一個  SilverlightApplication1 吧。

資料

由於 TreeView 是樹狀結構的顯示元件,要顯示 TreeView 的 DataBinding,當然需要一個樹狀結構的資料。以下是目錄的結構程式。

using System.Collections.Generic;

namespace SilverlightTreeView1
{
    public class MyFolder
    {
        public MyFolder(string name)
        {
            Name = name;
        }
        public string Name { get; set; }

        public List<MyFolder> SubFolders { get; set; }
    }
    public class FolderHelper
    {
        public static List<MyFolder> GetAllFolder()
        {
            var folders = new List<MyFolder>()
                              {
                                  new MyFolder("A")
                                      {
                                                        SubFolders = new List<MyFolder>(){
                                          new MyFolder("A1")
                                              {
                                                                SubFolders = new List<MyFolder>()
                                                                                 {
                                                                                     new MyFolder("A11"),
                                                                                                new MyFolder("A12"),
                                                                                                new MyFolder("A13"),
                                                                                                new MyFolder("A14"),
                                                                                 }
                                              },
                                                        new MyFolder("A2"),
                                                        new MyFolder("A3")
                                                        }
                                      },
                                                    new MyFolder("B")
                                                        {
                                                            SubFolders = new List<MyFolder>(){
                                          new MyFolder("B1"),
                                                        new MyFolder("B2"),
                                                        new MyFolder("B3"),
                                                        new MyFolder("B4"),
                                                        }
                                                    }
                              };
            return folders;
        }
    }


}

使用 TreeView 元件

在MainPage.xaml 上拖入一個 TreeView 元件。

Xaml

<UserControl x:Class="SilverlightTreeView1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:SilverlightTreeView1="clr-namespace:SilverlightTreeView1">
        <Grid x:Name="LayoutRoot" Background="White">
        <sdk:TreeView Name="treeView1">
        </sdk:TreeView>
    </Grid>
</UserControl>

在 MainPage.xaml 中,建構子程式碼中設定 treeView1.ItemsSource

using System.Windows.Controls;

namespace SilverlightTreeView1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            treeView1.ItemsSource = FolderHelper.GetAllFolder();
        }
    }
}

運行程式後,結果如下圖

image

運作的結果只出現了最上層的兩個目錄,而且並沒有顯示我想要的目錄名稱。

修改1: 顯示目錄名稱

要顯示目錄名稱,其實很簡單,就是要在 xaml 中作一些變化。

<sdk:TreeView Name="treeView1">
    <sdk:TreeView.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" />
        </DataTemplate>
    </sdk:TreeView.ItemTemplate>
</sdk:TreeView>
TreeView.ItemTemplate的內容是內容的樣版,並且使用DataTemplate 作為資料繫結樣版。這裡使用了一個 TextBlock,並且繫結到 MyFolder 物件的 Name 屬性(Property)。
結果如下圖。
image

修改2:改成樹狀顯示

DataTemplate 只能顯示繫結物件的資料,並無法顯示樹狀結構。為了顯示樹狀結構,我們必須將資料顯示的樣版,改用HierarchicalDataTemplate

<sdk:TreeView Name="treeView1">
    <sdk:TreeView.ItemTemplate>
        <sdk:HierarchicalDataTemplate ItemsSource="{Binding SubFolders}">
            <TextBlock Text="{Binding Name}" />
            </sdk:HierarchicalDataTemplate>
    </sdk:TreeView.ItemTemplate>
</sdk:TreeView>

結果如下圖

image

程式碼下載

2010年12月2日 星期四

使用 TFS Power Tool 的 Best Practices Analyzer

微軟出了很多的 Best Practices Analyzer(BPA),幫助我們檢視電腦、資料庫、伺服器的設定。目前介紹的就是 TFS 的 BPA

SNAGHTML1590726

掃描完畢後,看一下報告。Oh! 好像有不少的問題。

SNAGHTML184e2de

再按一下 「Tell me more about this issue and how to resolve it」連結,就會顯示相關的訊息。

Team Foundation Server Power Tools September 2010

直到最近才知道 TFS Power Tool 又在10月皆出新的版本

移除舊版本

如果之前已安裝過舊的版本,記得先移除舊版本。下圖是舊的版本。

image

安裝

安裝仍和之前版本相同。記得安裝前先將 Visual Studio 2010 關閉。安裝時使用 Typical 的選項即可。

也不知道為什麼,這個工具安裝時總是會花去5分鐘以上。明明該安裝檔 tfpt.msi 才 12.8 MB 啊!

使用

與之前的功能相同的就不說了。新功能可見這裡的說明。之後有機會再介紹吧!

2010年11月23日 星期二

Visual Studio 2010 做壓力測試 (load test),出現錯誤訊息

使用 Visual Studio 2010 做壓力測試 (load test)好一陣子。有一天做完後,,突然發生了錯誤訊息

Error occured running test. The loadTest completed but an error occurred while collecting the final results. Could not access the load test results repository: Incorrect syntax near 's'.  The label 'http' has already been declared. Label names must be unique within a uqery batch or stored procdure

檢視報表切到 Detail時顯示了下面的訊息

No detail exists at the selected point

使用 「Open and manage results」也找不到剛剛執行的測試結果。

原本想說將 load test 所使用的資料庫清空,重新執行loadtestresultsrepository.sql 就可以解決了。這招也不成功。

看來這是 Visual Studio 的 bug 了。

接著,我想了一下,之前為什麼可以?我修改了什麼後變成這樣了。於是我找到了原因。

我的 load test 中其中一個Web Performance Test 有問題。將該 Web Performance Test 移除,或者在 Test Matrix 中設該test 的比例為0,就可以正常儲存測試結果到資料庫。

為什麼這個 Web Performance Test 會造成 load test 的問題呢?這看起來是 Visual Studio 的 bug

2010年11月7日 星期日

TPL 學習日記(9): 使用TPL 應避免的事情1: Avoid Memory Allocation

有些事情,同樣在使用 TPL 時也需要避免。第一件事情就是使用記憶體的 allocation。

原因

記憶體的 allocation 會導致 GC,記憶體的回收變成的一項負擔。

解決方法1: 使用 stack,避免GC。

範例程式如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TPL_2 {
  class Program {
    static void Main(string[] args) {
      Console.WriteLine("Sequence");
      Console.WriteLine(Time(GoSeq));
      Console.WriteLine("Parallel, using heap");
      Console.WriteLine(Time(GoPalHeap));
      Console.WriteLine("Parallel, using stack");
      Console.WriteLine(Time(GoPalStack));
    }

    private static void GoSeq() {
      var q = (from i in Enumerable.Range(1, 20000000)
               select new { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    //anonymous type, 會使用 heap。導致GC 回收記憶體,效能較差
    private static void GoPalHeap() {
      var q = (from i in ParallelEnumerable.Range(1, 20000000)
               select new { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    //struct, 會使用 stack,效能較快
    private static void GoPalStack() {
      var q = (from i in ParallelEnumerable.Range(1, 20000000)
               select new MyClass { Value = i }).Max(i => i.Value);
      Console.WriteLine(q);
    }

    struct MyClass {
      public int Value;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}
執行結果

image

想不到吧!使用 Heap 反而使得 TPL 執行效果比不使用更差。改使用 struct 後就變快了。

解決方法2: 使用 Server GC

第二個方法是啟用 Server GC (伺服器記憶體回收),見<gcServer> 項目。簡單的說,使用這個方法,會讓CPU 的每個 Core 有獨立的 GC,因此回收的效能變快了。

.net framework 預設使用 workstation (工作站)的方式回收記憶體,也就是只有一個 GC。使用預設的 workstation 方式時, GC 必須考慮 multi-core 的 concurrency 問題,因此回收的效能較差。

我們加上一個 app.config ,如下

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <gcServer enabled="true" />
  </runtime>
</configuration>

執行結果

image

由結果得知,加上設 gcServer 的設定,雖然仍然使用了 heap,速度還是變快了,但無法與使用 stack相比。

TPL 學習日記(8): BlockingCollection 的 GetConsumingEnumerable

上一回提到了 BlockingCollection,順便提一個值得玩玩的事。

BlockingCollection 實作了IProducerConsumerCollection<T>. 此 interface 定義了製造者/消費者使用方式的安全執行緒集合。而BlockingCollection 是其中最典型的實作。BlockingCollection 可以使用GetConsumingEnumerable 取得加入/移除到集合中的項目。如下範例。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Threading;

namespace TPL_1 {
  class Program {
    static void Main(string[] args) {
      var primes = new BlockingCollection<int>();
      var t = Task.Factory.StartNew(
        () =>
        {
          //取得對BlockingCollection中新增或移除的每個項目
          foreach (var item in primes.GetConsumingEnumerable())
            Console.WriteLine(item);
        });
      for (int i = 0; i < 5000000; i++)
        if (IsPrime(i)) primes.Add(i);
      primes.CompleteAdding(); //結束新增
      t.Wait(); //直到 t 執行完
    }

   
    /// <summary>
    /// 是否為質數
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    static bool IsPrime(int input) {
      int bound = (int)Math.Sqrt(input);
      for (int i = 2; i <= bound; i++)
      {
        if (input % i == 0) return false;
      }
      return true;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}

執行起來,功能平凡無奇。好玩的是,這樣的技巧已經在之前的「讀取大型文字檔」中顯示過了,而GetConsumingEnumerable 以相同的方式實作出來。

CompleteAdding() 方法標示該集合不會再新增項目,因此 GetConsumingenumerable 的巡覽才會結束。

2010年11月5日 星期五

TPL 學習日記(7): Thread-Safe Collections

Parallel library 是一個多執行緒的一套 API,因此在使用時,也需要遵守多執行緒程式的規則。

以下,是一個「找出3 到 5,00,000 之間有多少個質數」的解法。

單一執行緒

首先,我們當然要寫出單一執行緒的作法,再寫成多執行緒的作法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace TPL_1 {
  class Program {
    static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 2000000; i++)
      {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
    }
    /// <summary>
    /// 是否為質數
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    static bool IsPrime(int input) {
      int bound = (int)Math.Sqrt(input);
      for (int i = 2; i <= bound; i++)
      {
        if (input % i == 0) return false;
      }
      return true;
    }

    static TimeSpan Time(Action a) {
      Stopwatch w = Stopwatch.StartNew();
      a();
      return w.Elapsed;
    }
  }
}

程式碼很簡單,其實就是將所有的質數找出並加進到一個使用 List<int> 的 instant 中(即 primes),並在最後算出總共有348515個質數。

使用 Parallel library (Wrong)

這次要使用 Parallel library。由之前的經驗,我們會寫成下面的程式碼。

static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 5000000; i++) {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new List<int>();
            Parallel.For(0, 5000000, i =>
            {
              lock(primes) //Watch this line
                if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
    }

注意到 //Watch this line 的那一行。由於在寫 multi thread 程式, concurrent 非常重要,我加了 lock(primes) 讓存取 primes 可避免掉一致性的問題。結果呢,Multithread 竟然比 Single thread 還慢!真不可思議。

image

使用 Parallel library (Correct)

不要自己發明輪子。上面例子的問題,是自行使用 lock 來解決 concurrency 的問題。如果是在 TPL 的世界中,該問題就應使用 System.Collection.Concurrent namespace 內的集合。以下範例加上新的解法 (BlockingCollection)

static void Main(string[] args) {
      //單一執行緒
      Console.WriteLine(Time(
    () =>
    {
      List<int> primes = new List<int>();
      for (int i = 0; i < 5000000; i++) {
        if (IsPrime(i)) primes.Add(i);
      }
      Console.WriteLine("Count = " + primes.Count);
    }
  ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new List<int>();
            Parallel.For(0, 5000000, i =>
            {
              lock(primes)
                if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
      Console.WriteLine(Time(
          () =>
          {
            var primes = new BlockingCollection<int>();
            Parallel.For(0, 5000000, i =>
            {
              if (IsPrime(i)) primes.Add(i);
            });
            Console.WriteLine("Count = " + primes.Count);
          }
        ));
    }

程式執行結果如下

image

結論

TPL 的程式已經很棒了,但有些細節的部份仍然要相當小心,否則甚至比不用還差。

Share with Facebook