.NET非同期処理(async-await)を制御する、様々な方法

async-awaitベースの非同期処理を制御する方法をまとめました。コードはわざと冗長に書いています。

概要:

  • Taskベースのワーカースレッド生成
  • 他の非同期I/Oと連携してタスクを制御する方法
  • TaskとLINQを応用して、多量の計算を安全に並列実行させる方法
  • Taskを使っていない非同期処理をTask化する方法
  • 非同期処理のキャンセルの実現方法
  • WinRT・ユニバーサルWindowsアプリケーション(UWP)での非同期処理とTaskの連携方法

読む前に補足

C#でTaskやasync-awaitを使った非同期処理の書き方を探しているのであれば、ポイントに絞って書いた、こちらの記事をお勧めします: 「できる!C#で非同期処理(Taskとasync-await)」

Taskクラスの使用例として、ワーカースレッドを起動するという例が良く挙げられます。本記事も最初にTask.Runによるワーカースレッドの例を紹介していますが、本来のTaskクラスの役割はワーカースレッドの管理ではなく、非同期処理の統一された操作にあります(非同期I/Oもワーカースレッドも同じように「非同期処理」として管理可能にする)。

また、async-awaitの使い方を調べていてこの記事にたどり着いた場合は、「基本的にTask.Runは使わない」と言う事を頭の片隅に置いておいて下さい。特殊な場合を除いて、明示的にTask.Runでワーカースレッドを操作する必要はありません。非同期メソッドが返すTaskクラスをawaitする事に集中すれば、問題なくコードを記述できる筈です。

違いが分からなくなったり混乱した場合は、この前提を思い出してみて下さい。

この記事の続きとして、非同期処理における例外を扱った記事もあります: 「.NET非同期処理(async-await)と例外の制御」


Task.Runの基礎

TaskクラスのRunメソッドを使用すると、ワーカースレッドを使用して独自の処理を非同期化(タスク化)出来ます。

public static async Task SampleTaskRun1Async()
{
	// ワーカースレッドが生成される
	Task task = Task.Run(new Action(() =>
		{
			// 任意のCPU依存性処理...
		}));

	// 処理が完了するのを非同期的に待機
	await task;

	Console.WriteLine("Done!");
}

Task.Runメソッドは、.NET4.5未満ではTask.Factory.StartNewと同じです。

Task.Runを使用した場合、戻り値としてTaskクラスのインスタンスが取得出来ます。
従来は、Threadクラスを使用してワーカースレッドを直接生成して管理していましたが、Task.Runで返却されたTaskを使用すれば、非同期処理と同じようにワーカースレッドの操作を待機する事が出来ます。

また、Threadクラスを使った場合、ワーカースレッドの待機は、Thread.Joinメソッドを使いますが、このメソッドはハードブロックしてしまいます。非同期処理の世界で行儀よく振る舞うためには、待機すべきあらゆる箇所でTaskクラスが必要となります。

# 厳密にはGetAwaiterメソッドの実装が担保しますが、細部なので省略。また、WinRTでは、IAsyncActionやIAsyncOperationでも待機出来ます(後述)

Taskを使用する優れた点は、他のタスクとの合成が簡単であることです。

public static async Task SampleTaskRun2Async(Stream dataStream, byte[] data)
{
	Task task1 = Task.Run(new Action(() =>
		{
			// 任意のCPU依存性処理...
		}));

	Task task2 = Task.Run(new Action(() =>
		{
			// 他の任意のCPU依存性処理...
		}));

	// ネイティブな非同期I/O処理
	Task task3 = dataStream.WriteAsync(data, 0, data.Length);

	// 全ての処理が完了するのを非同期的に待機
	Task combinedTask = Task.WhenAll(task1, task2, task3);
	await combinedTask;

	Console.WriteLine("All done!");
}

Task.WhenAllメソッドは、指定されたタスク群が全て完了するのを待機する、新しいTaskを返します。
上記のように、CPU依存性の(つまり、ワーカースレッドで実行する)非同期処理と、ネイティブなI/O操作の非同期処理を、全く同じように扱う事が出来ます。

Task.Runを使うと簡単にワーカースレッドを制御出来るので、従来のThreadクラスは、

  • スレッド名を割り当てる必要がある
  • COMアパートメントを設定する必要がある
  • スレッドローカルストレージに残骸が残るような状況を完全に破棄したい

と言うような場合にだけ使用すれば良いでしょう。


Task.Runの戻り値

Task.Runは戻り値を返すことが出来ます。

public static async Task SampleTaskRun3Async(int count)
{
	// Func<T>デリゲートを使用することで、処理の戻り値を返す事が出来る
	Task<double> task = Task.Run(new Func<double>(() =>
		{
			// 任意のCPU依存性処理...
			var r = new Random();
			return Enumerable.Range(0, count).Select(index => r.Next()).Average();
		}));

	// 処理が完了するのを非同期的に待機
	double result = await task;

	Console.WriteLine("Done: {0}", result);
}

Task.Run引数のデリゲートが戻り値を返す「Func<T>」である場合、返されるTaskも「Task<T>」となり、awaitすると戻り値が返されます。


Task.WhenAllで、結果の集約

戻り値を返すタスクと、Task.WhenAllとLINQを使って応用すると、大量のデータを効率よくワーカースレッドに流し込んで並列実行させ、かつ全て完了するのを待機するという、今までなら出血しそうなコードが、非常に簡単に安全に記述出来ます。

public static async Task SampleTaskRun4Async(int count)
{
	// 計算を非同期に実行するタスクを、count個列挙する
	// 1, 1+2, 1+2+3, 1+2+3+4, ...
	IEnumerable<Task<long>> tasks =
		Enumerable.Range(1, count).
		Select(index => Task.Run(() => Enumerable.Range(1, index).Sum(v => (long)v)));

	// 全ての処理が完了するのを非同期的に待機
	long[] results = await Task.WhenAll(tasks);

	Console.WriteLine("Done: [{0}]", string.Join(",", results));
}

慣れていないと分かり難いかも知れません。この処理は、以下のように動作します。

taskrun2この方法は、タスク(中身はワーカースレッド)を指定された個数分起動し、全てが完了するのを待ちます。直感的には、大量のワーカースレッドが生成され、コンテキストスイッチングで飽和してまともに動作しないように見えますが、実際はそうなりません。Task.Runは、スレッドプールからワーカースレッドを取得しますが、スレッドプールの総スレッド数は、効率よく実行できる程度に調整されています(図の例では、最大で4個のスレッドが次々と計算を処理します)。

taskrun11このスクリーンショットは、Process Explorerでスレッドに割り当てられているCPUサイクルを見たものです。このテストを実施したマシンは、4コア2論理スレッドなので、システム上は8スレッド使えます。実際にほぼ8スレッドだけがアクティブに動作し、大量のワーカースレッドで飽和する事が無い事が分かります。

PLINQ(並列LINQ)と比べると、タスクを集約可能な式として実装する必要があるため、PLINQのように自然に拡張する事は出来ませんが、パフォーマンスの予測がしやすい事が利点です。


Taskの存在しない世界に、Taskを導入する

このように、処理に紐づいたTaskがあれば、非常に応用範囲が広くなります。しかし、そもそも処理の完了をTaskで担保しない場合はどうでしょうか。例えば、.NET CLRのイベントは一種のコールバックなので、対応するTaskが存在しません。

より具体的な例で考えます。WPFのボタンは、クリックされるとClickイベントが発火します。普通はこれをフックして、ハンドラとなるラムダブロックやメソッドで処理を実装します。この時、擬似的なTaskで発火状態を置き換える事が出来れば、様々な応用が可能になります。

処理の完了を疑似的なTaskで表現するのに、「TaskCompletionSource<T>」クラスが使えます。

// (コードビハインドで書いていますが、推奨はしません。また、エラーチェックを省略しています)
private async void OnInitialized(object sender, EventArgs e)
{
	// 文字列を返すことが出来るTaskCompletionSourceを準備
	var tcs = new TaskCompletionSource<string>();

	// ボタンがクリックされたら、テキストボックスの文字列をTaskCompletionSourceを介して間接的に返却する
	button.Click += (s, e) => tcs.SetResult(textBox.Text);

	// 非同期で待機する
	string inputText = await tcs.Task;

	// 結果をテキストブロックに表示
	textBlock.Text = inputText;
}

SetResultメソッドを呼び出すと、待機しているタスクが戻り値を伴って継続します。

上記の例は、ボタンのハンドラから直接テキストブロックに表示すれば良いので、何の為に複雑にするのかわからないかも知れません。イベントをタスク化する利点は、先ほど示したように合成が簡単だからです。

// (コードビハインドで書いていますが、推奨はしません。また、エラーチェックを省略しています)
protected override async void OnInitialized(EventArgs e)
{
	// ボタン1がクリックされたら、テキストボックス1の文字列をTaskCompletionSourceを介して間接的に返却する
	var tcs1 = new TaskCompletionSource<string>();
	button1.Click += (s, e) => tcs1.SetResult(textBox1.Text);

	// ボタン2がクリックされたら、テキストボックス2の文字列をTaskCompletionSourceを介して間接的に返却する
	var tcs2 = new TaskCompletionSource<string>();
	button2.Click += (s, e) => tcs2.SetResult(textBox2.Text);

	// 両方のボタンがクリックされるまで待機
	string[] inputTexts = await Task.WhenAll(tcs1.Task, tcs.Task2);

	// 結果をテキストブロックに表示
	textBlock.Text = string.Join(",", inputTexts);
}

上記の例では冗長に書きましたが、例えば動的に任意の個数で生成したテキストボックスやボタンの列に対して簡単に拡張・記述量の大幅削減が出来そうです。

// (コードビハインドで書いていますが、推奨はしません。また、エラーチェックを省略しています)
protected override async void OnInitialized(EventArgs e)
{
	// itemCount_個だけUI部品を生成し、関連付けられたTask群を返す
	IEnumerable<Task<string>> tasks =
		Enumerable.Range(0, itemCount_).
		Select(index =>
		{
			var textBox = new TextBox();
			var button = new Button();
			stackPanel.Children.Add(textBox);
			stackPanel.Children.Add(button);
			
			var tcs = new TaskCompletionSource<string>();
			button.Click += (s, e) => tcs.SetResult(textBox.Text);

			return tcs.Task;
		});

	// 全てのボタンがクリックされるまで待機
	string[] inputTexts = await Task.WhenAll(tasks);

	// 結果をテキストブロックに表示
	textBlock.Text = string.Join(",", inputTexts);
}

もう一つ例を示します。ワーカースレッドにThreadクラスを使わなければならない状況でも、TaskCompletionSource<T>を使ってタスク化する事が出来ます。

// COMアパートメントを指定して、独立したワーカースレッドを実行する
public static Task<TResult> RunEx<TResult>(
	Func<TResult> action,
	ApartmentState state = ApartmentState.STA)
{
	// スレッド終了を通知するTaskCompletionSource
	var tcs = new TaskCompletionSource<TResult>();

	// ワーカースレッドを生成する
	var thread = new Thread(new ThreadStart(() =>
		{
			try
			{
				// 処理本体を実行して、戻り値を得る
				var result = action();

				// Taskに終了を通知
				tcs.SetResult(result);
			}
			catch (Exception ex)
			{
				// 例外をTaskに通知
				tcs.SetException(ex);
			}
		}));
	thread.SetApartmentState(state);
	thread.Start();

	// Task<TResult>を返す
	return tcs.Task;
}

このRunEx<T>は、Task.Run<T>のように使え、かつCOMのアパートメントを指定可能にする例です。

TaskCompletionSource<T>は、例外を通知する事も出来ます。SetExceptionメソッドを呼び出すと、非同期待機しているタスクで指定された例外がスローされます。

なお、何故かTaskCompletionSource<T>のジェネリック引数を指定しないバージョン(つまり、非同期待機後に戻り値を受け取らないバージョン)は存在しません。但し、Task<T>は、Taskクラスを継承しているので、適当な引数型を与えて、完了もSetResultに適当な値を渡して代用する事が出来ます。


非同期処理のキャンセル

非同期処理をキャンセルするためのインフラも標準で用意されています。

// 指定されたデータをストリームに指定回数出力する
public static async Task SampleTaskRun5Async(
	Stream stream,
	byte[] data,
	int count,
	CancellationToken token)
{
	for (var index = 0; index < count; index++)
	{
		// キャンセル要求があれば、例外をスローして中断する
		token.ThrowIfCancellationRequested();

		// 非同期I/O処理も、中断を可能にする
		await stream.WriteAsync(data, 0, data.Length, token);
	}
}

CancellationToken構造体は、関連する非同期処理全体にわたってキャンセル要求を通知するための構造体です。キャンセルが発生したかどうかを保持する、一種のフラグのようなものです。呼び出し側が何らかの事情でキャンセル要求を行うと、このトークン経由で通知されるので、非同期処理を中断させる事が出来ます。

ThrowIfCancellationRequestedメソッドは、キャンセルが要求されているかどうかを検出し、キャンセルしていればその場でOperationCanceledExceptionをスローします。上の例ではトークンをWriteAsyncにも渡しているので、あまり大きな意味はありません(キャンセルされればWriteAsync内で例外がスローされる)。出来るだけ早くキャンセル要求に反応したい場合に、ThrowIfCancellationRequestedを使う事が出来ます。

ところで、CancellationTokenはどこから来るのでしょうか? CalcellationToken自体は、キャンセル要求を通知するための抽象的な構造体に過ぎません。自分で呼び出し側のコードを書いている場合は、CancellationTokenSourceクラスを使う事で、制御可能なトークンを用意する事が出来ます。

// (コードビハインドで書いていますが、推奨はしません。また、エラーチェックを省略しています)
protected override async void OnInitialized(EventArgs e)
{
	// CancellationTokenSourceクラスを準備(これでキャンセルを通知出来る)
	var cts = new CancellationTokenSource();

	// ボタンがクリックされたら、キャンセルを通知する
	button.Click += (s, e) => cts.Cancel();

	try
	{
		// 時間のかかる非同期処理にキャンセルトークンを渡す
		Task task = SampleTaskRun5Async(stream_, data_, 100000, cts.Token);

		// 非同期で待機する
		await task;

		// 結果をテキストブロックに表示
		textBlock.Text = "Output completed!";
	}
	catch (OperationCancelledException)
	{
		// 結果をテキストブロックに表示
		textBlock.Text = "Output canceled...";
	}
}

仮に、キャンセルさせたい場合の実行コンテキストが存在しない場合は、Registerメソッドを使って、コールバック化する事が出来ます。以下の例は、手元に実行コンテキストがある(awaitで待機している)ため、あまり適切な例ではありませんが、例えばネイティブAPIで処理開始後、処理がスレッドから完全に切り離されるような状況下で使用する事が出来ます。

// 指定されたデータをストリームに指定回数出力する
public static async Task SampleTaskRun6Async(
	Stream stream,
	byte[] data,
	int count,
	CancellationToken token)
{
	// キャンセル要求に対して、コールバックで反応する
	// (下記の実処理とは無関係に、キャンセル時にコールバックが発生する)
	var index = 0;
	token.Register(() =>
		// (実際にはここでキャンセル処理を行う)
		Console.WriteLine("Operation canceled: Written count={0}", index));

	// 実処理
	for (; index < count; index++)
	{
		token.ThrowIfCancellationRequested();
		await stream.WriteAsync(data, 0, data.Length, token);
	}
}

この他にも、WaitHandleプロパティから、待機可能なWin32カーネルオブジェクトが取得出来ます。Win32 APIと連携して同時にキャンセルを待機させたい場合などに使う事が出来ます。


WinRT・ユニバーサルWindowsアプリ(UWP)での非同期処理

WinRTの世界では、WinRT APIの呼び出しに対する非同期処理が「IAsyncAction」「IAsyncOperation」インターフェイスで表現されています。これらは「IAsyncInfo」インターフェイスを継承していて、Taskクラスによく似ていますが、追加の情報(進行状況など)を通知する能力を持たせることが出来るようになっています。

IAsyncActionとIAsyncOperationは、非同期処理の結果として戻り値を返すかどうかが異なります。Taskクラスの非ジェネリックバージョンとジェネリックバージョンに対応します。そして、これらのインスタンスは「await」で待機可能です。

.NET Task WinRT IAsyncInfo
戻り値無し Task IAsyncAction
戻り値あり Task<TResult> IAsyncOperation<TResult>

そのようなわけで、大体Taskクラスと同じように扱えますが、Task.WhenAllなどを使ったタスクの合成は出来ません。Task.WhenAllの引数は、Taskクラスしか受け付けないからです。そこで、IAsyncActionやIAsyncOperationをTaskクラスに変換する、ヘルパーメソッドが用意されています。

// WinRT非同期処理群を待機して、結果をテキストブロックに反映する
public static async Task SampleTaskRun7Async(IEnumerable<IAsyncOperation<string>> asyncOperations)
{
	// IAsyncOperation<string>をTask<string>に変換
	// (WindowsRuntimeSystemExtensions.AsTask拡張メソッド)
	IEnumerable<Task<string>> tasks =
		asyncOperations.Select(asyncOperation => asyncOperation.AsTask());

	// すべてが完了するのを待機出来るタスクに変換
	Task<string[]> combinedTask = Task.WhenAll(tasks);

	// 完了を待つ
	string[] results = await combinedTask;

	// 結果を反映
	textBlock.Text = string.Join(",", results);
}

AsTask拡張メソッドを使用すると、Taskに変換できます。一旦Taskに変換できれば、これまでの例と同じように応用が可能です。また、TaskをIAsyncActionやIAsyncOperationに逆変換する拡張メソッド(AsAsyncActionAsAsyncOperation)も用意されています。

WinRTの世界では、CancellationTokenに相当する、キャンセル管理用のクラスやインターフェイスがありません。その代わり、IAsyncInfo.Cancelメソッドがあり、直接キャンセルを要求する事が出来ます。TaskとCancellationTokenの世界をシームレスに結合する場合は、AsTaskのオーバーロードにCancellationTokenを渡すことによって、Cancelメソッドの操作も自動的に行わせる事が出来ます。

ユニバーサル Windows プラットフォーム向けアプリ開発オンライン講座が開催されます

uwpmvp来週2015/6/10に、次期Windows 10で対応される「ユニバーサル Windows プラットフォーム」のためのアプリ開発オンライン講座が開催されます。

概要

世界最大級の IT 技術カンファレンス //Build 2015 で発表された Windows 10 の開発技術をわかりやすく解説

配信日時 | 6 月 10 日 (水) 20:00 ‐

アプリ開発者、ウェブ開発者向けに //Build 2015 で発表された「ユニバーサル Windows プラットフォーム」アプリケーションの開発概要について、Microsoft MVP を受賞したアプリケーション開発のスペシャリストが日本語で解説します。

  • これまでの開発と何か違うところはあるの?
  • Universal App って、具体的に何なの?
  • Windows as a service で提供される OS を見据えたアプリケーション開発者が押さえておくべきキーワードはどのようなもの?

当日はこうした開発者の知りたい「なぜ」に生放送でお答えする、ライブ Q & A 形式のセッションにてお届けします。ご自身のアイデアやソフトウェアを世界中の幾億ものユーザーに向けて提供するのに最適な、「新しい」プラットフォームをご紹介するこの機会をどうぞお見逃しなく!

スピーカー:初音 玲 さんと 酒井 達明 さん

登録はこちら


WindowsプラットフォームのスペシャリストとAzureのスペシャリストということで、凄く期待出来そうです(と、ハードルを上げておいたりw)。

Visual Studioが何度も無関係のプロジェクトをビルドするのは、奴のせいかもしれない – NuGet編

NuGetまるで続きがあるかのようなラノベタイトルですが、一話完結です (;´Д`)

ものすごーく長い間、頭を悩ませてきた問題の一つが、先日ようやく判明したので共有しようと思います。
Visual Studio 2013(より前のバージョンも多分)で、比較的中規模~大規模なソリューション(沢山プロジェクトが含まれている)において、クリーンビルド後にも再びビルドが走って「イラっと」する問題です。

何度やっても発生する

GitなどのSCMからソースコードだけを取得して、完全なクリーンビルドを行っても起きます。これが起きると、以後のビルドは「リビルド」の如く、依存関係上ビルド不要なものを次々とビルドしてしまうので、開発効率が極めて悪くなります。

「お前はバッチファイルでビルドしてるだろ」

と言いたくなります。

何故か、自分一人で開発している場合には発生しません。また、使用しているPCによって、起きたり起きなかったりします。PCによって挙動が大体決まるものの、同じPCで試行しても現象が変わる事があります。規模の問題なのか、プロジェクトに関わっている誰かが何かをしたのか、PCが腐っているのか、Visual Studioの問題なのか、ずっと謎のままでした。

原因

謎な要因は、得てして複雑な背景があるものですが、今回もそうでした。

  • プロジェクトには「NuGet」でパッケージ導入しているものが含まれます。とあるパッケージ「ABC」のバージョンが「1.0.0」とします。
  • ビルドされたバイナリは、共通のフォルダに格納されます。例えば、「$(SolutionDir)$(PlatformName)\$(ConfigurationName)」です。今回のシステムでは、アセンブリを動的にロードする要件があるので、プロジェクトのサブフォルダに出力されるとデバッグがやりにくいのです(というか、デフォルトでこうして欲しいんだけどなぁ)。

チーム内の誰かが、新たにプロジェクトを追加する場合に、自分が関わったプロジェクトの構成をひな形に、新しいプロジェクトを追加します。この時、手動でアセンブリ参照を追加します。その中には「ABC.1.0.0.dll」が含まれます。手動で追加しているので、当然ファイルダイアログから「packages」フォルダ内を参照して追加するわけですが、これによって「NuGetとは何の関係もない事になっているアセンブリを追加した」事になります。

さて、コアメンバーが新しいNuGetパッケージ群に対応させるため、NuGetのパッケージを更新したとします。ここで、SafeUpdateをしないと、「ABC」パッケージには「1.0.0」と「1.0.3」のような、二つのバージョンが混在する可能性があります(SafeUpdateは、Update-Packageに-Safeを付ける)。packagesフォルダ内には、この二種類のパッケージが配置され、プロジェクトファイルも新しいバージョンを使うように修正されます、

プロジェクトがNuGetで構成されていれば。

手動でアセンブリ参照を追加したプロジェクトは、当然packages.configファイルを持っていません。そのため、NuGetのUpdateで自動的に参照が更新されません。しかし、SafeUpdateしなかったためにpackagesフォルダには「1.0.0」のパッケージが残っており、ビルド自体には成功します。ここに問題があります。

複数のプロジェクトがビルドされると、これらの混在したバージョンのアセンブリが、何度も「共通の」出力先フォルダにコピーされます。その結果、1.0.0と1.0.3のアセンブリが、まるでロシアンルーレットのように上書きコピーされ、ビルド完了時にはどちらのバージョンが配置されたのか分かりません。すると、次回ビルド時に、何の変更をしていなくても、Visual Studioが「あ、ビルドしなきゃ」と思っていそいそとビルドを始めます。

この問題は、マルチコアによる平行ビルドを実行すると顕著に表われます。環境によって発生したりしなかったりし、再現性が無いのでかなりイライラします orz

解決方法

  • NuGetで複数のバージョンを同時に使わない事。確認するには、ビルド後にpackagesフォルダ内を見て、複数のバージョンのパッケージが配置されていない事を確認する。もし、複数のバージョンが含まれていたら、どのプロジェクトが犯人かをチェックして修正する。csproj内のパスを修正し、packages.config内のバージョンも修正する。一旦、古いバージョンに合わせた後、SafeUpdateで一括更新すると楽かもしれない。
  • プロジェクトに、直接アセンブリ参照している構成が無い事を確認する。これはプロジェクトが多いと地味に苦痛かもしれない。
  • NuGet絡みで書きましたが、「共通の出力先フォルダ」を使い、異なるバージョンの同一のアセンブリ名のファイルを配置していると、同様の問題が発生する可能性があります。心当たりある場合は、確認してみましょう。

感想

久しぶりに一発ビルド完了した状態を見た。感慨深かった。
NuGet、ALMの一部としてみると脆弱で辛い。PSで構成とか、悪夢だ…

Roslyn for Scriptingで、あなたのアプリケーションにもC#スクリプトを!!

dotnet_logoいよいよ、Visual Studio 2015リリースが近づいてきました。今回はC#的にはあまり大がかりな拡張がありませんが、内情としてはC#コンパイラのインフラが「Roslyn」に正式対応するという事で、地味に大きな変更となっています。

Roslynは、MSのオープンソース戦略としては早い段階で公開され、それ以来、パブリックな場で将来のC#コンパイラの仕様検討などが行われています。勿論、ソースコードも「オープンソース」として公開されており、自分でいじりたければフォークも可能です。そろそろ概要を掴んでおこうと考えている方向けに、いくつかリンクを張っておきます。

なお、この記事には続編があります:「真・Roslyn for Scripting! あなたのアプリケーションにもC#スクリプトを!!」


ちょっとだけ背景

csc今まで、C#のコンパイラは「csc.exe」で、.NET Frameworkに付属していました。このプログラムはアンマネージコード(多分、C++)で書かれており、メンテナンスが大変そうだなと思います。また、Visual Studioが、ソースコードエディタ内でインテリセンスを表示させるために、csc.exeとは「別に」、独自にソースコードのパースを行っており、言語仕様の変化に合わせて二重にメンテナンスしなければならないという問題もありました。更に、Resharperなどのサードパーティプラグインも、この内部実装にはアクセスできないため、またもや同じようなパーサーを独自に書かなければならず、どう考えても不健全です。

そこで、C#のソースコードを解析し、いわゆる「構文木」をオブジェクト表現可能な形でパースする、ニュートラルな独立したライブラリを実装する事にした… というのがRoslynです。当初は「Compiler as a service」 (InfoQ)なワードだけが取りざたされて、何か大きなサービス的なもののように誤解されたこともありましたが、要するにコンパイラのための(低レベルな)インフラをライブラリ化して、様々な環境から共通利用出来るようにしたものです。

# なお、C#, C#と言ってますが、VB.netもRoslyn化されています。

さて、Roslynのパーサーの説明をしようとすると大変そうなので (;´Д`) この記事では「スクリプティング」に焦点を置きたいと思います。

所で、実はこの分野は、.NETのマルチプラットフォーム展開を行う「Mono」の方が先行していて、MonoのC#コンパイラは C#で書かれているので、既に同じようなインフラが存在する事になります。Monoのインフラを使ったスクリプティングインフラとして、「scriptcs」が存在します。このプロジェクトは、スクリプティングのモジュール化に必要な「スクリプトライブラリ」を作る事が出来たり、スクリプトから参照するライブラリをNuGetから取得(実行時に配置、即使用可能)出来たりと、スクリプト環境としての熟成を図っているようです。また、Roslynがリリースされたことで、scriptcsのインフラを、MonoかRoslynかで選択可能にしようとしています。

Monoプロジェクトは今後、.NET Coreプロジェクトと相互に情報交換して、.NET純正のソースコードのMonoへの取り込みや、Monoの相互運用性(Linux対応など)の.NETへの取り込みを行う事を表明しています。また、C#コンパイラのインフラについては、Roslynベースに統一する方針のようです。


スクリプティング・ホスティング

自分で書いた何らかのアプリケーションに、スクリプティング機能を載せたいと思ったことはありませんか? 例えば、何らかのゲームを作っているとして、このゲームにC#のスクリプティング機能を搭載出来たら、アドベンチャーゲームやロールプレイングゲームのシナリオ進行、シミュレーションゲームの敵AI調整に C# のスクリプトが使えるようになるのです。ゲームの世界ではluaやpython辺りをよく聞きますね。でも、本体をC#で書いているなら、スクリプトもC#で書きたいものです。

別の例として、Officeを挙げます。Officeには「Visual Basic for Application」という、VBチックなスクリプト環境があります。が、VBA、書きたくないです… VBAは徐々にフェードアウトの方向になっていると思いますが、代わりにC#でスクリプトが書けたら、もうちょっとOfficeに前向きになりそうです(Excel向けであれば、商用プラグインが既にあります:FCell)。MicrosoftはOfficeでC#を使えるようにするか? は、まだまだ分かりませんが、Roslynが公開されたことで、その可能性は高くなったと思います。

Roslynのコアは、C#のパーサーライブラリです。そして、極めて依存性が低く設計されています(Portable class library化されています)。が、パースしても構文木が得られるだけで、これが実行可能になるわけではありません。この構文木を食わせて、スクリプトとして実行可能にするインフラが「Roslyn for scripting」です。

流れは以下のようになります。

Roslyn Roslyn for scripting
スクリプト(文字列) → 構文木 → IL変換(コンパイル) → 実行

Roslyn for scriptingは、構文木をコンパイルしてIL命令に変換し、更に実行まで行います。IL変換は、System.Reflection.Emitを使用して動的にILを生成します。Emitは環境によって使用出来ない(例:ストアアプリ環境であるWinRTではEmitは使えない)ため、Roslyn本体には含めなかったのだと推察しています。現在のところ、Roslyn for scriptingは.NET4.5以上の環境でのみ使用可能です。

話が複雑になってきましたか? いやいや、Roslyn for scriptingは、ものすごく簡単に使えます!


Hello! C# scripting!!

roslyn1Roslyn for scriptingを使ってスクリプト環境を作る場合、殆どRoslynを意識する必要はありません。とりあえずHello worldをやってみましょう。.NET 4.5以上のコンソールアプリケーションのプロジェクトを作ってください。次に、NuGetで以下のパッケージを導入します。まだ正式版ではないので、「リリース前のパッケージを含める」を選択して下さい。

# もうRCでGoLiveなので、インターフェイスに大きな変更は無いと思われます。
# 最近、NuGetの調子が悪いようなので、上記パッケージが一覧に表示されない場合は、パッケージマネージャコンソールから “Install-Package” コマンドでインストールすると確実です。

導入出来たら、Mainメソッドに以下のようにRoslyn for scriptingの実装を書きます。

using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	class Program
	{
		static void Main(string[] args)
		{
			// Console.WriteLine("Hellow C# scripting!");
			var script = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting!\");");
			script.Run();
		}
	}
}

roslyn2これだけです! さぁ、実行してみましょう!! 簡単ですね! 簡単過ぎて、応用性が無いのでは? と思うかもしれませんが、大丈夫です。追々分かると思います。

ところで、ソースコードの先頭にコメントを入れておきましたが、興味深い事に気が付きましたか?

Consoleクラスは、本来はSystem名前空間に存在しますが、指定していません。これは、事前に「using System;」相当の定義が組み込まれているからです。
また、このコード自体、名前空間の宣言も、クラス宣言も、メソッド宣言もありません。スクリプト環境でこれらを書くことももちろん出来ますが、スクリプト環境ではこれらを省略可能にしているのです。そのため、いきなり実装本体が来ても、全く問題ありません。


スクリプト環境固有の課題

上記のように、スクリプティングの世界では、単にソースコードをリアルタイムで解析・実行するだけではなく、普通のソースコードとは異なる状況に対処する必要があります。以下にそのような課題を挙げます。

  • 記述の省略を可能にする
  • 逐次処理を可能にする
  • ホスト環境と通信する

記述の省略を可能にする

前述のように、スクリプト環境では色々省略して記述出来た方が便利です。ホストする側で、事前にusingを成立させておくことが出来ます。「System」名前空間は既定で定義されていますが、独自の名前空間も定義しておくことが出来ます。そのためには、事前にScriptOptionsクラスを用意して、オプションとして指定します。

using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	class Program
	{
		static void Main(string[] args)
		{
			// using System.Collection.Generic; 相当
			var options = new ScriptOptions().
				WithNamespaces(
					"System.Collections.Generic").
				WithReferences(
					typeof(object).Assembly);

			// var list = new List<string> { "ABC", "DEF" }; System.Console.WriteLine(string.Join(",", list));
			var script1 = CSharpScript.Create(
				"var list = new List<string> { \"ABC\", \"DEF\" }; System.Console.WriteLine(string.Join(\",\", list));",
				options);
			script1.Run();
		}
	}
}

ScriptOptionsや他のクラスもそうですが、Roslynでは「イミュータブルオブジェクト」を全面的に採用しています。イミュータブルオブジェクトとは、文字列(System.String)や日付構造体(System.DateTime)のように、インスタンスの内容が不変である事を保証した実装の事です。一度生成されたインスタンスは、中身が変更されることはありません。

上記の例では「WithNamespaces」メソッドを呼び出していますが、これによって、最初に生成したScriptOptionsクラスのインスタンスを変更するのではなく、新たなScriptOptionsクラスのインスタンスを生成しています。以下がWithNamespacesの定義です。

/// <summary>
/// Creates a new <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptOptions"/> with the namespaces changed.
/// </summary>
public ScriptOptions WithNamespaces(IEnumerable<string> namespaces)
{
	// (内部実装)
}

このように、WithNamespacesを呼び出すと、名前空間をオプションとして追加した「新しいScriptOptions」を生成し、戻り値として返却します。そのため、単にWithNamespacesを呼び出しただけでは、元のインスタンスには何も変更が加わっていない事になるので注意が必要です。

roslyn3さて、名前空間の定義は「WithNamespaces」メソッドで行いますが、ScriptOptionsの定義を空のインスタンスから始めた(new ScriptOptions())ので、mscorlibへの参照を追加しておく必要があります。それが「WithReferences」メソッドです。

なお、最初の例でオプションを省略しましたが、省略時はmscorlibへの参照と、System名前空間の定義だけが行われた状態になっています。

こうして必要な定義を追加したオプションを生成出来れば、あとは、CSharpScript.Createの引数にオプションを渡すことで、これらの定義が解釈されます。一般的には、「System」「System.Collections.Generic」「System.Linq」「System.Threading.Tasks」などが定義されていた方が、利便性が向上するでしょう。あるいは、これらの定義をホスト側のApp.configや設定ファイルから読み込むようにすれば、更に柔軟性が高くなります。


逐次処理を可能にする

スクリプト環境では、ユーザーがコンソールからインタラクティブにコードを記述する可能性があります。例えば、コマンドプロンプト(cmd.exe)やPowerShellでは、ユーザーがコマンドラインから入力した命令やコードが「Enter」キーの押下で、即実行されます。と言う事は、コンパイル実行の場合は、一度にすべてのコードを評価しなければならないのが、ちょっとづつ、断片的にソースコードが渡される可能性がある事を意味します。この状況を、とりあえず簡単に記述したのが以下の例です。

using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	class Program
	{
		static void Main(string[] args)
		{
			var script1 = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting for first line, and continue...\");");
			script1.Run();

			var script2 = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting for next line!\");");
			script2.Run();
		}
	}
}

単に、CSharpScriptのインスタンスを2回生成して実行しているだけです。これは問題ないでしょう。しかし、スクリプトコードの入力をユーザーがインタラクティブに実行している場合、もっと複雑な問題が発生する可能性があります:

using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	class Program
	{
		static void Main(string[] args)
		{
			var script1 = CSharpScript.Create(
				"var data = 123;");
			script1.Run();

			var script2 = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting with data: {0}\", data);");

			// "(1, 56): error CS0103: The name 'data' does not exist in the current context"
			script2.Run();
		}
	}
}

roslyn4コードは名前空間・クラス・メソッドなどの定義が無くても解釈されるのでしたよね? いきなり変数「data」が定義されていますが、スクリプトとしては問題ありません。問題はscript2です。このスクリプトで、data変数を参照しようとしていますが、コンパイルエラーが発生しています。

これは、script1とscript2が、相互に全く関係のないスクリプトとして実行しているためです。勿論、そのように意図している場合は問題ありませんが、インタラクティブに実行する場合は、このようにコードが分断されてしまう可能性は十分考えられます。

では、どうすれば良いでしょうか? script1で定義されたdata変数をscript2で参照可能にするには、「ScriptState」を使います。

using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	class Program
	{
		static void Main(string[] args)
		{
			var script1 = CSharpScript.Create(
				"var data = 123;");
			var script1State = script1.Run();

			var script2 = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting with data: {0}\", data);");
			script2.Run(script1State);
		}
	}
}

CSharpScript.Runメソッドの戻り値は、ScriptStateです。この中には、スクリプトを実行した結果や、スクリプトとして記憶しておくべき様々な情報が格納されています。これを、次のスクリプト実行時のRunメソッドの引数に渡すと、スクリプトはScriptStateの記憶を前提とした環境で動作します。この中にはdata変数の結果も格納されているので、script2も問題無く実行出来るようになるのです。

さぁ、もう、かなり自分のアプリケーションに組み込める気がしてきましたよね?! 次の問題が片付けば、大抵の環境に組み込んで、すぐに応用が可能ですよ!


ホスト環境と通信する

ここまでで、独立したスクリプトエンジンとして問題なく操作できるようになったはずですが、現実には、スクリプト側のコードから、ホスト環境のオブジェクトを操作したりしたい訳です。でなければ、スクリプト環境を組み込む意味がありませんよね。

前述のCSharpScript.Runメソッドの引数の定義を見て、疑問に思ったかもしれません。

/// <summary>
/// Runs this script.
/// </summary>
/// <param name="globals">An object instance whose members can be accessed by the script as global variables, or a <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptState"/> instance that was the output from a previously run script.</param>
/// <returns>
/// A <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptState"/> that represents the state after running the script, including all declared variables and return value.
/// </returns>
public ScriptState Run(object globals = null)
{
	// (内部実装)
}

Runメソッドの引数は「object」です。ここに渡していたのはScriptStateクラスのインスタンスなので、わざわざobjectと定義する意味が分からないかもしれません。実は、この引数にはScriptStateだけではなく、任意のクラスのインスタンスが渡せます。以下に例を示します:

using System;
using System.Linq;

using Microsoft.CodeAnalysis.Scripting.CSharp;

namespace HelloRoslynForScripting
{
	public sealed class HostObject
	{
		public int Value;

		public string CreateValues(int count)
		{
			var r = new Random();
			return string.Join(",", Enumerable.Range(0, count).Select(index => r.Next()));
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			var hostObject = new HostObject { Value = 123 };

			var script1 = CSharpScript.Create(
				"Console.WriteLine(\"Hello C# scripting with host object: Value={0}, Values=[{1}]\", Value, CreateValues(10));");
			script1.Run(hostObject);
		}
	}
}

ホスト側で用意した「HostObject」クラスのインスタンスを渡し、スクリプト側からアクセスしています。面白い事に、スクリプトからはグローバルな無名の要素としてアクセス出来ます。そのため、「Value」フィールドや「CreateValues」メソッドに、インスタンスやクラスの指定がありません。

roslyn5Runの引数に渡せるインスタンスは、クラスである必要があります(構造体はダメです。勿論、クラスのメンバーに構造体があっても問題ありません)。また、クラスはパブリックでなければなりません。恐らく、スクリプトがコンパイルされた際に、クラスのメタデータにアクセスする必要があるからでしょう。


残る課題

ここまで分かれば、後は応用だけです。スクリプト環境として考えられる他の課題は、scriptcsを見てみると分かるかもしれません。

  • インタラクティブな指令で、アセンブリ参照を追加する
  • インタラクティブな指令で、スクリプトをファイルとしてロードする
  • インタラクティブな指令で、NuGetなどのパッケージシステムをサポートする

つまり、インタラクティブなコンソールがコードの入力となる場合は、帯域外の指令で、ホストがスクリプティングのサポートをする必要があります。scriptcsでは、このような独自コマンドを定義(先頭が”:”で始まるコマンドが入力された場合は、スクリプトコードではなく独自に処理を行う。例えば”:load”でスクリプトライブラリをロードする)して対処しています。

さあ、これでC#スクリプトで何でも出来るでしょう!

Final LINQ Extensions II – 第三回 Center CLR 勉強会

finallinqextensions2第三回Center CLR勉強会で、Final LINQ Extensions IIのタイトルで登壇してきました。

ご静聴ありがとうございます。
前回網羅できなかった、細かい話の落穂広いと、標準の演算子を一通り説明しました。
標準演算子が使いこなせるようになると、LINQでの実装がはかどる事間違いなしですよ。

にしても、あーあ、やっちまいました (;´Д`) 次回もよろしくお願いします!!


オリジナルスライドはこちら: Final LINQ Extensions II.pptx

旧環境で、もはやDVDを使わない – Windows 10をISOイメージからインストールする

DVD小ネタです。最近のUEFI対応BIOSではなく、少し前のPCに対して、Windowsを素からインストールしたい場合、一番安全なのは USB DVDドライブから起動する事でした。しかし、DVDはメディアをDVD-Rに焼く必要があって面倒です。今回、Windows 10 Insider Previewを旧式のPCにインストールする際に、いい加減USBメモリからインストールしたいという欲求で、調べなおしたのでメモしておきます。

今頃か!という気も、自分でもするんですが (;´Д`)、以前はUSBメモリからブートさせてインストールをするには面倒な手順が必要で、DVD-Rメディアもそれほど高くなかったこともあり、必要に応じて焼き直して使っていたからです。


UNetbootinはダメ

unetbootinサクッと検索すると、最初に見つかったのがコレ。

UNetbootin

UNetbootin allows you to create bootable Live USB drives for Ubuntu, Fedora, and other Linux distributions without burning a CD. It runs on Windows, Linux, and Mac OS X.

これは発想がちょっと面白く、Linux等のディストリビューションをダウンロードしてUSBにコピーするまでを、全部やってくれます。オプションとしてISOイメージをUSBにコピーする事も出来ます。残念ながら、サイトにはっきり書いてない通り、これを使ってWindowsのISOイメージからブート可能なUSBメモリを作る事は出来ませんでした(正常終了しますが、ブートしません)。


本命 Windows 7 USB/DVDダウンロードツール

isoimager1マイクロソフト純正にこのようなツールがありました。

Windows USB/DVD Download Tool

CodePlexでソースコードも公開しています。また、日本語版もありますね。インストールして起動すると、ISOイメージを選択できます。今回はWindows 10 Insider Previewのx64版を選択。ちなみに、こちらで登録する事で、ダウンロードできます。

isoimager2次に、インストール先のUSBメモリを選択します。まあ、スクリーンショットを見ての通り、何も難しい事はありません。

isoimager3USBメモリはフォーマットされます。中身が必要な場合は、事前にバックアップしておきましょう。

isoimager4と、このように、非常に簡単にUSBメモリでブートするWindowsメディアを用意出来ます。このツール、改めて紹介するほどの物でもないのですが、知らなければ一度使ってみると良いと思います。とにかく、たまにしか再インストールをしない場合など、色々準備事項を忘れてしまう場合に重宝するかと思います。


酷いファーストインプレッション

fiwin10さて、Windows 10を手持ちの実機で試したかったのですが、すぐに入れられるのが放置されているAcer AspireOne AO751-Bk26だったので、これに入れてみました。第一世代Atom Z520で、元はWindows XPのマシンです。シングルコアなマシンで非常に非力。Windows 8.1も相当にきつい(主に描画がもたつく)のですが、Windows 10もその点はあまり変わらない感じです。但し、GMA500ドライバ(Vista)を入れれば、Huluの動画は問題なくSpartanで見る事が出来ました。

正直、先日の中華タブの方が数倍良いです(;´Д`)。まさかいないと思いますが、アップグレードを考えている人は、諦めて新しいマシンを買った方が良いですよ!

Microsoft MVP for .NETを受賞しました

msmvp320表題のとおり、2015.04期のMicrosoft Most Valuable Professionalを受賞しました。コミュニティでは様々な方に応援して頂いてありがとうございました!

MVPには受賞ジャンルがあり、私は「.NET」というカテゴリーで受賞しました(まだ、MVPの公式サイトからは検索できないようです)。


MVPのあれこれ

MVPは自薦で申し込みました。申し込み自体は去年の10月もしたのですが、落選 (;´Д`) ただ、引き続き活動の様子を見たいので、改めて申し込んで欲しいと書かれていたので、ヌケヌケと再応募して受賞、という流れです。

MVPの事を考え始めたのは去年の頭ぐらいからですが、周到に狙ったわけではありません。そもそも、MVPの受賞可否は非公開なので、どのような経緯で落選・受賞したのかは、最後まで分かりません。ただ、現在MVPとして活躍されている方のお話を聞く感じでは、ブログの情報発信よりもコミュニティ活動は重視される「らしい」と言う事を聞いていました。

10月と言えば、CenterCLRが発足ホカホカで、勉強会もまだ開いていなかったので、10月に応募しても落選するだろうなぁとは思っていました。

MVPへの自薦を考えている方は、年間4回募集があり、結果は次回の申し込み期間の直前に連絡がある事は覚えておきましょう。つまり、受賞するとしても結構長いスパンで時間がかかります。また、過去1年間のコミュニティ活動をまとめる必要があります(定型フォームがあるので、そこに記入します)。

MVPを受賞すると、様々な特典があるようです。何しろまだ受賞したてで詳細は良く判りませんが、分かり易い部分では、MSDN Ultimate Subscriptionのライセンスが付与されます(MVPの認定期間は1年間なので、ライセンスも1年間です)。他にもConnectの優先アクセスや、MVPメンバーだけのイベントへの参加権があるようです(NDA契約があるので、何かネタ情報を聞いてもバラせない、という制約はあります)。

個人的には、MSDNはCommunity Championshipでライセンスを貰っているし、会社でも使うので、それ以外の特典に期待しています。特に、MVP向けのイベントには都合が付けば参加したいです。


マイルストーンに「なった」

MVPの受賞は、それほど期待していたわけではありませんでした(期日が迫ってきたらそれなりに結果が気になりましたが)。実際、受賞したらしたで、嬉しいかな、ぐらいで考えていました。

しかし、実際に受賞してみて、突然、受賞したことがマイルストーンになった感が出てきました。

# それは「マイルストーン」じゃないというツッコミが飛んで来そうだw

コミュニティ活動を行うまでは、私の技術の視野はとても限定的でした。国内においても、技術にこれだけ熱意を持って取り組んでいる人が沢山いるという事実を殆ど知らず、自分の周りには皆無で諦めのような心境で、本を執筆したり大手サイトで記事を書くような人は、どこか遠い国の人達のように思えていました。

そういう自分だけの世界では、身に着けた技術を客観的に考える土壌は無く、果たして良いのか悪いのかもわからず、しかも業務でその技術を有効活用出来ていないと、自身に何の価値も見いだせないという、とっても辛い世界で悶々とするしかありませんでした。

今考えると、そういうフラストレーションは、MCPを次々と脈絡もなく取ってみるという行動に出ていたと思います(普通は、計画的にMCPを取得して上位MCP認定を狙うものです。お金もったいないww)。

で、色々あって、自分の会社を立ち上げてしばらくした頃、「どうもMSはWindows Azureに力を入れているらしい。自社でサービスを立ち上げるとしたら、こういう技術も吸収しておく必要があるだろう」、と考え、時間も割と自由に取れたので「セミナー」に参加してみるかと思いました。

一つ目のセミナーはマイクロソフト名古屋支部で行われましたが、Azureの概要に触れたぐらいで、もっと「濃い」話が聞けたら良いのに、というのが感想でした。それで、他にもセミナーが無いかと探したのです。中部圏でのセミナーは選択肢も限られてくるのですが、「Bar Windows Azure」とか言う「セミナー」があるらしい、と言うのを、どこからか見つけてきました。

adreana

ATND: Bar Windows Azure in Nagoya

今、上のATNDページを見てみても、ちゃんと「piano bar CLUB ADRIANA」と書いてあるわけですよ。まさか、本当に酒場的バーでこういうイベントをやるとは、鎖国人間には全く想像出来ていなかったのです。セミナーですよ、セミナー。だから、当日、店の前で「本当にここでやるのか?!」とウロウロした記憶がありますw

そして、その時のセッションの内容も衝撃的でした。「何でもかんでもブラウザベースはおかしいだろう」と思ってた口なので、「SignalRって何だよ?!」とか。更にさらっと高いレベルの解説が飛び交ってるこのバーの状況が理解できない… 結構ボー然としてました。

「こ、これは、この人たちは、一体何者なのか? ここは本当に自分の知っているITの世界なのか?!」 と。

# 今見たら全員MVPですね… 無知もイイ所です。感謝してます

コミュニティ活動なるものを知った後は、次々と参加するようになりました。が、早々に登壇する機会を頂いて(いきなり「その話、LTしませんか?」だったので、正直、かなりビビってましたが)、自分で技術を整理して発表する、という事を繰り返して、ようやく思い出しました。

IT技術とは、閉鎖的で鬱屈した、決まった作業を淡々とこなすような作業的なものではなく、クリエイティブな取り組みなんだと。ここに至るまでの20年近く、楽しくおもしろかったはずのコンピューター技術が、取るに足らない社会の量産的ネジとして刻み込まれた「何か」から脱しなければ、自分は本当にダメになってしまう、と。

それから今に至るのですが、MVP受賞の審査内容は分かりませんが、受賞したことでこういった経緯を思い出して、改めてこれは「マイルストーンだったんだ」なぁと感慨深く感じたのです。


これからどうするか

変わらず、です。MVPの受賞は結果的に自分にとってのマイルストーンだったのですが、どちらにしてもこれまでのペースを基本に、コミュニティ活動を長く続けていきたいと思います。

MVPの受賞カテゴリは.NETなのですが、引き続き注力したい分野です。.NET Coreがオープンソースとなり、大きな変化がありました。今後どうなっていくのか、とても楽しみです。

scrumalliance2また、個人的にはもう一つの大きな柱として、認定スクラムマスターを取得した事があります。スクラムは実践する事が重要なのですが、この点では全くと言って良いほど実践できていないのが辛い所です。スクラムの実践・アジャイルな活動は、人間的な仕事の在り方、人間的な成長に繋がると思っているので、何とかそこにリーチして実践したい・共有したいなぁと思っています。

これからも応援、よろしくお願いします!

第一回 三重合同懇親会 MGK2015

第一回 三重合同懇親会が、三重県津市で開催されたので行ってきました。

mgk2015
名古屋でクロスコミュニティな集いを「名古屋合同懇親会 NGK2014B」として開催していたのを、三重でもやりたい! というところから始まった企画で、Center CLRの紹介とLTの二本立てで参加しました。

参加人数は、26名と、会場に対して結構ぎっちりで、中部圏からちょっと離れた(失礼!)場所なのに、参加人数にびっくりしました。

この懇親会で分かったのは、「三重、IoTが熱い!」と言う事です。LTで伊勢方面のIT歴史を紹介していただいたのですが、伊勢発祥のデバイスメーカーって、結構多いんですね。前から伊勢方面は(理由がわからないけど)過熱しているというイメージがあって、実際に下地があるんだなという事が(良い意味で)意外でした。

で、いつもながら準備時間が十分取れなかったのですが、CenterCLRの紹介と、CenterCLR的ネタを三重にかましてきましたよ!!


Center CLRにおこしやす(誰

Center CLRの紹介です。特に何って事もないです… いや、興味ある方、ぜひぜひ参加どうぞ!!


Hello! Intermediate Language

これ、本当ならLTでやるネタでもないんですが、以前にがりっちさんがNawaTechで頑張ってLT枠でショートセッションネタをやってたのを見て、チャレンジしてみようと思ってたので、やってみました。

超絶まくってたので、理解のほどはどうかな?という感じですが、ちゃんとスタックマシンの話もしましたよー

セッションで説明したコードはGitHubにアップしてあるので、見たかった!という人は確認してみてください。

オリジナルスライド:Hello_Intermediate_Language
GitHub:CenterCLR.EmitLiveDemo


今日は.NET MicroFrameworkに積極的にかかわっている方との情報交換も出来て、IoT全般のコミュニティの取り組みとか、もっと大きな技術者的業界生き残り戦略がどーのこーのとか、物理演算シミュレーションがアレとか、主婦と子供のITへの関わりとか、想像もしなかった盛りだくさんで面白かったです。次回も参加しますよ!

それでは、また。

さようなら、Windows 5。

とうとう近づいてきた

win20032
最近、良く目につくようになった、「Windows Server 2003移行」の文字。GoogleやBingで検索すると、技術情報よりも延命措置の有償サービスが上位に表示されます。私の業務環境では既にレガシーWindowsを扱っていない事もあり、現実的な問題がどんなものかは想像するしかない状態です。

公式がどこかいまいち良く判らないので、リンクを張っておきます:Windowsライフサイクルのファクトシート

Windows XPが2014年4月にサポート切れとなり、もうすぐWindows Server 2003のサポートも切れます(2015年7月15日)。つまり、Windowsとしてサポートが存在するバージョンはVista以降、という事になります。

Windows XPはともかくとして、Windows Server 2003は個人的にとても良い印象のWindowsでした。Windows XPと大体において同じカーネルを持ち、Windows XPのように色々ゴテゴテと付いていないOSと見た時、2003の扱いやすさが光るのです。

# ええと、Vistaや7が嫌いと言う訳ではなく、むしろ「見た目」で入る製品も好きなのですが、手の内でほぼコントロール出来る感も、それはそれで良いと思ってます。

また、Windows 5系列は、初めてx64のサポートが追加されたバージョンでもあります。初物だけあって、Windows XP x64 Editionは色々な制約があって、ユーザーサイドで必ずしも良いOSとは言えませんでしたが、Iteniumと違ってx86にやさしい(互換性が高い)事が良く、後続のWindows x64の基礎を作りました。また、同じx64カーネルを持つWindows Server 2003 x64はサーバー向けという事もあって、より良く改良されていました。

# Windows Server 2003 x64はWindows XP x64よりも、全ての面で安定性が高かった。
# デバイスドライバについて言えば、サードパーティはWindows XP x64を半ば無視ししていたのに対し、2003 x64での開発に注力していたことも大きいです。


カーネルモードの5

win20031Windows XPやWindows Server 2003は、Win32プログラミングを行う方なら知っているかもしれませんが、GetVersionEx APIで得られる「メジャーバージョン番号」番号は「5」です。この記事のタイトルはそこから来ている訳ですが、このバージョン5はWindows 2000からWindows Server 2003 R2までの系譜です。分かりつらいですね。Windows 5の一覧を載せましょう:

 

  • Version 5.0 – Windows 2000 x86 (Windows 2000 Professional/Server/Advanced server)
  • Version 5.1 – Windows XP x86 (Professional/Home/Starter)
  • Version 5.2 – Windows Server 2003 x86/x64/Itenium (無印/R2) / Windows XP Professional x64/Itenium

Windows XP x64は実は5.2なので、2003と同じ扱いである事が分かります。既にどうでもいいトリビアですが、Windows 2000、つまり5.0は、唯一のNEC PC-9800シリーズ向けの実装が存在しました。

# そういえば、NTの頃はMIPSやらAlphaやらもサポートされました(一時はSPARCの噂までありました)。

以前私はWDKを使って、Windows向けのストレージ系ツールを設計・開発していました。このブログのWDKのカテゴリでいくつか記事を書いているのは、その時得られた知見に基づいたものです。このツールは、Windowsのカーネルモードドライバ(フィルタードライバ)を含んでいて、ディスクI/Oを監視して色々操作するものでした。

テストの為に、ユーザーモードのアプリケーションで非常に高負荷なI/Oを発生させ、ntfs.sys・フィルタードライバ・物理ドライバ間のパフォーマンス測定を行っていました。その時に、Windows 5系列の各スタックは、全体的にパフォーマンスが高いなと思ったのです。

Windows 6(つまりVista以降)と5の間には、「乗り越える事が出来ない」程のパフォーマンス差がありました。残念ながら開発はWindows 7・2008R2までであったため、最新のWindows 8でどの程度改善されたのかは分かりませんが、それでもWindows 5系列の方が良いと思われます。その位の差がありました。

# VistaではSSE2への最適化も謳われていましたが、「そんなものは無かった」程の違いです。結局「Vista重い」というのは、エンドユーザーの正直な感想で大体合っていたなーと思います。

勿論、この例はシンセティックなベンチマークのようなものです。現実的なパフォーマンスの測定には、他のあらゆる要素が関係してきます。また、システムの利便性は、ベンチマーク結果だけで測る事は出来ません。ですが、ベンチマーク速度の話が出て来る度に、「あぁ、5のスタックだけ使えたらなぁ」という、不毛な妄想をしたものです。

実行環境について:Windows XP以上は、今の所HYPER-Vのゲスト拡張機能は問題なく動作するようです。Windows 2000については、リリースビルドは問題ありませんが、デバッグビルド(デバイスドライバデバッグ用の、アサーションが大量に挿入されたWindows)は動作しませんでした。


終焉

Windows XPの当初の印象は良くない物でした。また、Windows XPの終盤では、IE6の不具合と思しき症状も放置されたためにWindows Updateが出来ないという、最悪の状態でしたが、最後には修正されてほっとしました。

さて、現在、主に.NETの世界の住人として気になる事は、CLRとのバージョンの関係でしょう。Windows 5の系列は、大ざっぱにはCLR 2.0(.NET 2.0~3.5)のバージョンの扱いと重なります。つまりは、Windows 5が終焉するのに合わせて、CLR 2.0も終焉と考えても良いかなと言う点です。

Microsoft .NET Frameworkサポートライフサイクルポリシーを見ると、何だかややこしい事になっていますが、開発者の目線で見ると、もはやNuGetのサポートは避けられず、Visual StudioがWPFベースで実装されている事もあり、Visual Studio 2012での開発が最低レベル(少なくともCLR 4.0)という状況です。

NuGetは2010向けもありますが、パッケージの方が2010で検証していないものもあり、結局NuGetを使うなら、ほぼ最新のVisual Studioを使わなければならないため、古いバージョンでの開発は互換性問題を解決するよりも、問題を引き起こす方が大きいという、想像もしなかった状況になってきています。

また、NuGetのパッケージを使い、そのパッケージに脆弱性が見つかった場合、殆どのプロジェクトは最新版に対して修正を行うと思われます(そもそもNuGetにはバージョン分岐の概念が無い)。すると、脆弱性対策を行うには、パッケージを最新に更新すれば良い(=最新に更新しなければならない)、という事を意味します。

# 既にASP.NETのプロジェクトは、思いっきりNuGetに依存しているので、これを回避するのはかなり辛いです。

つまり、今後の開発は、同じプラットフォームバージョンが維持されることを前提に開発するのはほとんど不可能で、常に最新に追従し続けなければならない、事を意味します。これは既にスマートフォンベースの開発(iOS及びAndroid)では当たり前になってきており、好むと好まざるとにかかわらず、Windowsもその位の、素早く、変化に追従できる開発が求められる、という事です。

マイクロソフト周辺での事情の変化も大きい、IT全体での変化もとても大きなものでした。

そんな今日の状況を感じつつ、Windows 5を見送りたいと思います。

RIP Windows 5 versions.

win20033


※なお、文中で「GetVersionEx API」の話を持ち出しましたが、このAPIは非推奨です。「Version Helper functions」を使って下さい。

WordPress-ja on Git

wpe

WordPress日本語版を配布しているサイトの配布アーカイブをスクレイピングして、自動的にGitリポジトリを構築するコードを書いたので、GitHubで公開します。このコードと、実際にこのツールで生成したGitリポジトリも公開したので、勝手にforkやcloneしてやってくださぃ。

SourceTree見て分かる通り、差分が一目瞭然です。バージョンタグを打ってあるので、保守しているバージョンからブランチを切って、独自に弄った部分を自分のリポジトリで管理して、かつ新しいバージョンの差分も容易に取り込めます!

以下、README.mdのコピーです。


お品書き

  • このリポジトリは、WordPress日本語サイトで公開されている正式版のWordPressパッケージを、バージョン毎にGitにコミットしたものです。
  • 公開されているファイルは、配布ページから取得しています。zip形式を展開してコミット・タグ付け、を繰り返して生成しています。
  • 今の所、ベータやRCは含めていません。
  • ファイル群のフェッチとコミットの生成には、私が作ったCenterCLR.WordPressJaRepositoryGeneratorを使用しています。残念ながら私はPHP使いではないので、コードはC#です。
  • fork、clone等ご自由にどうぞ。新しいバージョンが公開されたら、新しいコミットを追加する予定です。
  • 当然ですが、PullRequestは受け付けませんw ワークフロー的に残念ですが、修正依頼は本家に直接して下さい。

補足

  • 私が対応しているリアルお客さんへの対応が、いい加減面倒になってきたので、ツール一発でGitのリポジトリを生成できるようにしました。
  • 使い方としては、GitHubでそのままforkして使うか、プライベートリポジトリをメインに、リモートとしてこのリポジトリを追加してfetchし、そこからブランチやマージをすると、とても幸せに管理できます。
    お客さん毎・更にバージョン毎など、好きにブランチ切って修正入れれば、本家がバージョンアップした時も、マージがとっても楽です。
    更には、ホスティングマシンでGitクライアントが使えれば、展開も超簡単になりますね!
  • 本当はen本家とja本家が全部Gitでやってくれたら、作る人も使う人もすごく楽が出来ると思うんだけどなぁ… ここでこういうリポジトリを公開しても、「本家じゃないから」とか思われそうだし。
    ちなみにen本家はGitリポジトリを公開していますが、これってSubversionのmirrorなんですよね。これも凄く残念感漂う…
    そんなわけで、ツールも公開しておくので、どうしてもプライベートリポジトリで閉じたい場合はこれを使って下さい。