スレッドセーフなコレクションクラス メモ

こういうクラスがあるのよということを箇条書きで。

System.Collections.Concurrentで定義されているクラスを使う。
System.Collections.Concurrent 名前空間 ()

BlockingCollectionクラス

BlockingCollection の概要


  • スレッドセーフなキューとして使用できる

  • スレッドセーフなのでlock制御が必要ない

  • TryTakeメソッドでDeQueueする

  • AddメソッドでEnQueueする

  • IsAddingCompletedプロパティでエンキュー側(プロデューサーというらしい)が追加しているかが確認できる。

スレッドとタスク

スレッド


  • 同時実行可能なスレッド数はプロセッサ数に依存する。

  • .netではスレッドひとつに1MBのサイズが必要。

  • スレッドの切り替えは高コスト。(コンテキストスイッチが発生する)

  • 大量のスレッド作成はかえって逆効果。スレッドを作成するのではなく、タスクを生成したほうがよい。

タスク


  • タスクはスレッドに割り当てられ実行される。このあたりの一連の管理(スレッド作成、タスク割当て)はスレッドプールで行なわれる。

  • タスクの切り替えにはコンテキストスイッチが発生しないので、低コスト。

  • スレッドプールは効率よく処理が行なえるようにスレッドの生成、タスクの割り当てを行なうしくみ。

動作確認コード

  public class SomethingProcesser
  {
    public int Result { get; set; }

    public List<SomethingProcesser> Children { get; set; }

    public SomethingProcesser()
    {
      Children = new List<SomethingProcesser>();
      Result = 0;
    }

    public void Proc()
    {
      _proc(this);
    }

    private void _proc(SomethingProcesser proc)
    {
      foreach (var child in proc.Children)
      {
        _proc(child);
        child.Proc();
      }

      Thread.SpinWait(10000);
    }
  }

  public interface IProcesser
  {
    void DoProc();
  }

  public class SomethingTask : IProcesser
  {
    private SomethingProcesser processer;

    public SomethingTask(SomethingProcesser processer)
    {
      this.processer = processer;
    }

    public void DoProc()
    {
      _doProc(0);
    }

    private void _doProc(int procIdx)
    {
      if (processer.Children.Count > procIdx)
      {
        Task task = new Task(() => {
          var p = processer.Children[procIdx];
          _doProc(++procIdx);
          p.Proc();
        });
        task.Start();
        task.Wait();
      }
    }
  }

  public class SomethingThread : IProcesser
  {
    private SomethingProcesser processer;

    public SomethingThread(SomethingProcesser processer)
    {
      this.processer = processer;
    }

    public void DoProc()
    {
      _doProc(0);
    }

    private void _doProc(int procIdx)
    {
      if (processer.Children.Count > procIdx)
      {
        Thread task = new Thread(() => {
          var p = processer.Children[procIdx];
          _doProc(++procIdx);
          p.Proc();
        });
        task.Start();
        task.Join();
      }
    }
  }
public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
  }

  private SomethingProcesser processer;

  private void Form1_Load(object sender, EventArgs e)
  {
    processer = new SomethingProcesser();
    for (int i = 0; i < 100; i++)
    {
      var child = new SomethingProcesser();
      processer.Children.Add(child);
      for (int j = 0; j < 100; j++)
      {
        child.Children.Add(new SomethingProcesser());
      }
    }
  }

  private void button1_Click(object sender, EventArgs e)
  {
    var sw = new Stopwatch();
    sw.Start();
    processer.Proc();
    sw.Stop();
    Console.WriteLine("直列実行:{0}ms", sw.ElapsedMilliseconds);
  }

  private void button2_Click(object sender, EventArgs e)
  {
    //スレッド
    var sw = new Stopwatch();
    var thread = new SomethingThread(processer);
    sw.Start();
    thread.DoProc();
    sw.Stop();
    Console.WriteLine("スレッド使用:{0}ms", sw.ElapsedMilliseconds);
  }

  private void button3_Click(object sender, EventArgs e)
  {
    //タスク
    var sw = new Stopwatch();
    var task = new SomethingTask(processer);
    sw.Start();
    task.DoProc();
    sw.Stop();
    Console.WriteLine("タスク使用:{0}ms", sw.ElapsedMilliseconds);
  }
}

実行結果

何度か実行したが、速度的には
1,タスク 2,スレッド 3,直列実行
の順番だった
メモリ使用率はどの方法も変わらず。

処理時間例)
直列実行:1782ms
タスク使用:917ms
スレッド使用:1397ms

正規表現クラスでの置換

お恥ずかしい話ですが、RegexクラスのReplaceメソッドがこんなに便利なんて知りませんでした。
正規表現で指定しなくてもMatchした最小単位で置換処理が行なわれるのですね。

//hello world を Hello Worldに置換する
class Program
{
 static void Main(string[] args)
  {
    var str = "hello world";
    var regex = new Regex(@"(\W*)(\w+)");
    Console.WriteLine(regex.Replace(str, (MatchEvaluator)delegate(Match m){
      return m.Groups[1].Value + CultureInfo.CurrentCulture.TextInfo.ToTitleCase(m.Groups[2].Value);})
    );
  }
}

プラットフォーム呼び出しによるデータのマーシャリング

まだ完全には理解できていない部分があります。下記参照元を参考にして
とりあえず動くようにはなりましたが、構造体文字列メンバー値が正しく取得できない等
一部おかしなところがあります。

サンプルソース

統合アーカイバプロジェクト の 7-ZIP32.DLL が提供している機能を使用するサンプル
//使用する機能分だけマーシャリングしている。
//オリジナルの定義情報は、7-ZIP32.DLL付属のヘッダーファイルなどを参照すること。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MarshalingSample
{
    //#define FNAME_MAX32		512

    //typedef struct {
    //    DWORD 			dwFileSize;
    //    DWORD			dwWriteSize;
    //    char			szSourceFileName[FNAME_MAX32 + 1];
    //    char			dummy1[3];
    //    char			szDestFileName[FNAME_MAX32 + 1];
    //    char			dummy[3];
    //}	EXTRACTINGINFO, *LPEXTRACTINGINFO;
    [StructLayout(LayoutKind.Sequential)]
    public struct EXTRACTINGINFO
    {
        public UInt32 dwFileSize;
        public UInt32 dwWriteSize;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 513)]
        public char[] szSourceFileName;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 3)]
        public char[] dummy1;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 513)]
        public char[] szDestFileName;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 3)]
        public char[] dummy;
    }

    //typedef struct {
    //    EXTRACTINGINFO exinfo;
    //    DWORD dwCompressedSize;
    //    DWORD dwCRC;
    //    UINT  uOSType;
    //    WORD  wRatio;
    //    WORD  wDate;
    //    WORD  wTime;
    //    char  szAttribute[8];
    //    char  szMode[8];
    //} EXTRACTINGINFOEX, *LPEXTRACTINGINFOEX;
    [StructLayout(LayoutKind.Sequential)]
    public struct EXTRACTINGINFOEX
    {
        public EXTRACTINGINFO info;
        public UInt32 dwCompressedSize;
        public UInt32 dwCRC;
        public UInt16 uOSType;
        public UInt32 wRatio;
        public UInt32 wDate;
        public UInt32 wTime;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 8)]
        public char[] szAttribute;
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.TBStr, SizeConst = 8)]
        public char[] szMode;
    }

    public class Zip
    {
        //typedef BOOL CALLBACK ARCHIVERPROC(HWND _hwnd, UINT _uMsg, UINT _nState, LPVOID _lpEis);
        public delegate bool ARCHIVERPROC(IntPtr _hwnd, UInt16 _uMsg, UInt16 _nState, IntPtr _ipEis);

        //int   WINAPI SevenZip(const HWND _hwnd, LPCSTR _szCmdLine, LPSTR _szOutput, const DWORD _dwSize);
        [DllImport("7-zip32.dll", EntryPoint = "SevenZip")]
        public static extern int DeCompress(IntPtr _hwnd, string _szCmdLine, StringBuilder _szOutPut, UInt32 _dwSize);

        //BOOL WINAPI SevenZipSetOwnerWindowEx(HWND _hwnd, LPARCHIVERPROC _lpArcProc);
        [DllImport("7-zip32.dll", EntryPoint = "SevenZipSetOwnerWindowEx")]
        public static extern bool SetCallBack(IntPtr _hwnd, ARCHIVERPROC callback);
    }
}


//7-ZIP32.DLL 機能を使用したサンプルアプリケーション
//アプリケーションに*.zipファイルをドロップすると解凍を行うことができる
//コールバック関数内で、解凍進捗をコンソールに出力している

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace MarshalingSample
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_DragDrop(object sender, DragEventArgs e)
        {
            var dialog = new FolderBrowserDialog();
            if (dialog.ShowDialog() != DialogResult.OK)
                return;
            
            var filename = ((String[])e.Data.GetData("FileDrop"))[0];

            var cmd = "-hide e \"{zip}\" -o\"{out_path}\"".Replace("{zip}", filename);
            cmd = cmd.Replace("{out_path}", dialog.SelectedPath);
            var buffer = new StringBuilder();

            Zip.SetCallBack(this.Handle, new Zip.ARCHIVERPROC(ProgressCallBack));
            Zip.DeCompress(this.Handle, cmd, buffer, 0);
        }

        private bool ProgressCallBack(IntPtr _hwnd, UInt16 _uMsg, UInt16 _nState, IntPtr _ipEis)
        {
            EXTRACTINGINFOEX exinfo = (EXTRACTINGINFOEX)Marshal.PtrToStructure(_ipEis, typeof(EXTRACTINGINFOEX));

            if (exinfo.info.dwFileSize > 0)
                Console.WriteLine("TotalSize:{0}  CurrentSize:{1}", exinfo.info.dwFileSize, exinfo.info.dwWriteSize);

            return true;
        }

        private void MainForm_DragEnter(object sender, DragEventArgs e)
        {
            var filename = ((String[])e.Data.GetData("FileDrop"))[0];

            if (Path.GetExtension(filename) == ".zip")
                e.Effect = DragDropEffects.Copy;
            else
                e.Effect = DragDropEffects.None;
        }
    }
}

列挙処理可能なクラスの実装

列挙クラス定義

namespace EnumeratorSample
{
    /// <summary>
    /// 数値範囲の列挙を行う2
    /// </summary>
    class RangeEnumerable2
    {
        private int from;
        private int count;
        private int step;

        /// <summary>
        /// 初期化
        /// </summary>
        /// <param name="from"></param>
        /// <param name="count"></param>
        /// <param name="step"></param>
        public RangeEnumerable2(int from, int count, int step)
        {
            this.from = from;
            this.count = count;
            this.step = step;
        }

        /// <summary>
        /// 列挙子を返す
        /// </summary>
        /// <returns></returns>
        public IEnumerable<int> GetEnumerable()
        {
            for (int i = from; i < from + count; i+=step)
            {
                yield return i;
            }
        }
    }

    /// <summary>
    /// 数値範囲の列挙を行う
    /// </summary>
    class RangeEnumerable : IEnumerable<int>
    {
        /*
         * IEnumerable<T>の実装バージョン。
         * このインターフェイスが非ジェネリックバージョンを継承しているので
         * すべて実装する場合はIEnumerator実装クラスが必要になってしまう。
         * 
         */

        private int from;
        private int count;

        /// <summary>
        /// 初期化
        /// </summary>
        /// <param name="from">開始値</param>
        /// <param name="count">カウントする数</param>
        public RangeEnumerable(int from, int count)
        {
            this.from = from;
            this.count = count;
        }

        #region IEnumerable<int> メンバ

        public IEnumerator<int> GetEnumerator()
        {
            for (int i = from; i < from + count; i++)
            {
                yield return i;
            }
        }

        #endregion

        #region IEnumerable メンバ

        /*
         * 異なるインターフェイス間で同名のメソッドが存在している場合の記述方法。詳細は下記参照。
         * ・どのインターフェイス実装かをフルネームで書く。
         * ・この場合はアクセス修飾子は書かない。(書くとビルドエラーになる)
         * 
         *明示的なインターフェイスの実装 (C# プログラミング ガイド)  
         * http://msdn.microsoft.com/ja-jp/library/ms173157.aspx
         */

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new EnumeratorSample.NonGenelic.RangeEnumrator(from, count);
        }

        #endregion
    }
}

namespace EnumeratorSample.NonGenelic
{
    /*
     * IEnumeratorインターフェイス実装は下記を参照
     * http://msdn.microsoft.com/ja-jp/library/system.collections.ienumerator.current%28VS.80%29.aspx
     * 
     */

    class RangeEnumrator : System.Collections.IEnumerator
    {
        private int[] array;
        private int idx;

        /// <summary>
        /// 初期化
        /// </summary>
        /// <param name="from"></param>
        /// <param name="count"></param>
        public RangeEnumrator(int from, int count)
        {
            array = new int[count];
            for (int i = 0; i < count; i++)
                array[i] = from + i;
            idx = -1;
        }

        #region IEnumerator メンバ

        public object Current
        {
            get
            {
                try
                {
                    return array[idx];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        public bool MoveNext()
        {
            idx++;
            return idx < array.Length;
        }

        public void Reset()
        {
            idx = -1;
        }

        #endregion
    }
}

上記列挙クラスの使用

/// <summary>
/// Window1.xaml の相互作用ロジック
/// </summary>
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        //列挙処理
        foreach (var val in new RangeEnumerable(1, 10))
        {
            Console.WriteLine("Value:{0}", val);
        }

        //非ジェネリックでの列挙処理
        System.Collections.IEnumerable enumerable = (System.Collections.IEnumerable)new RangeEnumerable(11, 10);
        foreach (int val in enumerable)
        {
            Console.WriteLine("Value:{0}", val);
        }

        //IEnumerable<T>を返すメソッドでの列挙処理
        foreach (var val in new RangeEnumerable2(20, 10, 3).GetEnumerable())
        {
            Console.WriteLine("Value:{0}", val);
        }
    }
}

C#3.0メモ1

主にLINQまわり1

3.0での省略記法について

データ型は型推論により決定される。
その為、省略して書かれていても型はしっかりと決まっている。

クラス定義の省略記法

public class Parson
{
    public String Name { get; set; }
    public String Sex { get; set; }
    public String Belong { get; set; }
}

//この書き方は、ParsonがNewされた後に各項目へ値をセットする。
//その為、ReadOnlyなメンバなどにはセットできない。
Parson[] parsons = {
   new Parson(){Name = "山田太郎", Sex="男", Belong="営業"},
   new Parson(){Name = "勝山次郎", Sex="男", Belong="営業"},
   new Parson(){Name = "山中真奈美", Sex="女", Belong="技術"},
   new Parson(){Name = "としこ", Sex="女", Belong="秘書"},
   new Parson(){Name = "越中銀次郎", Sex="男", Belong="広報"},
   new Parson(){Name = "大鳥昭信", Sex="男", Belong="社長"}
};

LINQでの反復処理

Enumerableクラスで定義されているメソッド・IEnumerableクラスの拡張メソッドで、ごにょごにょすることが多い。

シンプルなクエリ

//select句には、クエリを実行したときに生成される値の型を指定します。
//省略した場合は、範囲変数と同じデータ型のシーケンスが返される。
//qのデータ型はIEnumerable<int>
var q = from x in Enumerable.Range(1, 10) select x;  
foreach (var rec in q)
    Console.WriteLine(rec.ToString());

クエリ同士の結合

//商品分類
private class ItemKind
{
    public int KindId { get; set; }
    public String KindName { get; set; }
}

//商品
private class Item
{
    public int ItemId { get; set; }
    public int KindId { get; set; }
    public String ItemName { get; set; }
    public int ItemPrice { get; set; }
}

//クエリ実行
private class JoinQueryTest
{
	private ItemKind[] kinds;
	private List<Item>items = new List<Item>();
	private Random rmd;

	public JoinQueryTest()
	{
		rmd = new Random();

		kinds = new ItemKind[]
		{
		    new ItemKind() { KindId = 1, KindName = "分類1" },
		    new ItemKind() { KindId = 2, KindName = "分類2" },
		    new ItemKind() { KindId = 3, KindName = "分類3" },
		    new ItemKind() { KindId = 4, KindName = "分類4" },
		};

		for (int i = 0; i < 10000; i++)
		{
		    items.Add(new Item(){ItemId = i + 1
		                            , ItemName = "アイテム" + i.ToString()
		                            , ItemPrice = 10000 - i
		                            , KindId=rmd.Next(1,4)
		    });
		}
	}

	public void DoTasukigakeQuery()
	{
		var sw = new System.Diagnostics.Stopwatch();

		//下記クエリのようなJoinしないたすきがけは非常に遅いので使用しないこと。
		sw.Start();
		var q1 = from x in kinds
		         from y in items
		         where x.KindId == y.KindId && x.KindId == 2
		         select new { Name = y.ItemName, Kind = x.KindName };
		sw.Stop();
		Console.WriteLine("Query1 Define Time:{0}", sw.ElapsedMilliseconds);
		sw.Reset();
		sw.Start();

		StringBuilder sb = new StringBuilder();
		foreach (var rec in q1)
		    sb.Append("Name:" + rec.Name + "   Kind:" + rec.Kind.ToString());
		sw.Stop();
		Console.WriteLine("Query1 ResultOutputInfo  Count:{0}   Time:{1}", q1.Count(), sw.ElapsedMilliseconds);
		sw.Reset();
	}

	public void DoJoinQuery()
	{
		var sw = new System.Diagnostics.Stopwatch();

		sw.Start();
		var q2 = from x in items
		         join y in kinds on x.KindId equals y.KindId
		         where x.KindId == 2
		         select new { Name = x.ItemName, Kind = y.KindName };
		sw.Stop();
		Console.WriteLine("Query2 Define Time:{0}", sw.ElapsedMilliseconds);
		sw.Reset();
		sw.Start();

		StringBuilder sb = new StringBuilder();
		foreach (var rec in q2)
		    sb.Append("Name:" + rec.Name + "   Kind:" + rec.Kind.ToString());
		sw.Stop();
		Console.WriteLine("Query2 ResultOutputInfo  Count:{0}  Time:{1}", q2.Count(), sw.ElapsedMilliseconds);
		sw.Reset();
	}
}

//[実行結果]
//1回目
//Query1 Define Time:0
//Query1 ResultOutputInfo  Count:3308   Time:14
//Query2 Define Time:2
//Query2 ResultOutputInfo  Count:3308  Time:16

//2回目
//Query1 Define Time:0
//Query1 ResultOutputInfo  Count:3354   Time:10
//Query2 Define Time:0
//Query2 ResultOutputInfo  Count:3354  Time:2

//3回目
//Query1 Define Time:0
//Query1 ResultOutputInfo  Count:3392   Time:8
//Query2 Define Time:0
//Query2 ResultOutputInfo  Count:3392  Time:3

//4回目
//Query1 Define Time:0
//Query1 ResultOutputInfo  Count:3325   Time:27
//Query2 Define Time:0
//Query2 ResultOutputInfo  Count:3325  Time:3

//5回目
//Query1 Define Time:0
//Query1 ResultOutputInfo  Count:3329   Time:14
//Query2 Define Time:0
//Query2 ResultOutputInfo  Count:3329  Time:4