忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


C#5.0入門:asyncとawait

先日せっかくVS11 Developer Preview版をインストールしましたから、C#の最新機能を使ってみたいと思います。
asyncawaitです。

この2つのキーワードをみる前に準備段階として
こんなコードを考えてみましょう:
<Window x:Class="CSharp5Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Button Click="Button_Click"/>
</Window>

using System;
using System.Windows;

namespace CSharp5Test
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            heavyLogic();
            Title = "now : " + DateTime.Now;
        }

        private void heavyLogic()
        {
            System.Threading.Thread.Sleep(3000);
        }
    }
}


でかいボタンが表示されたウィンドウが出てきます。

00785dcb.jpg


このボタンをクリックするとどうなるでしょうか?
上のC#コードから予想できると思います。
3秒経過するとウィンドウのタイトルを現在時刻に書き変えます。
が、その間ブロックするためボタンは固まった状態からなかなか戻りません。
重い処理をすれば、UIが固まるというわけです。
ユーザーはストレスがたまるでしょう。



asyncとawait

そこで登場するのがasyncawaitキーワードです。
これは不思議な魔法を使って、Button_Clickが重い処理にたどり着くと制御を返して、重い処理は別スレッドで実行し、重い処理が終わるとメソッドの続きを元のスレッドで実行してくれます。

using System;
using System.Threading.Tasks;
using System.Windows;

namespace CSharp5Test
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            await heavyLogicTaskAsync();
            Title = "now : " + DateTime.Now;
        }

        private Task heavyLogicTaskAsync()
        {
            return Task.Run(() => heavyLogic());
        }

        private void heavyLogic()
        {
            System.Threading.Thread.Sleep(3000);
        }
    }
}


新しく加わったheavyLogicTaskAsync()は、重い処理heavyLogic()を別スレッドで実行してくれます。
Taskクラスは.net4.0から登場した、別スレッドでの実行をつかさどるクラスです。
このTaskはasyncやawaitと連携して非同期処理を行ってくれます。

今度はボタンを押しても、ありがたいことに固まりません。
クリックした後すぐに押せる状態に復帰します。
Button_ClickがheavyLogicTaskAsync()に差し掛かった瞬間制御を返すからです。
もっともタイトルが変化するのにかかる時間は相変わらず3秒ですが・・・

しかし本当に重い処理を別スレッドで走らせてくれているのでしょうか?
そしてその後の処理は元のスレッドで走らせてくれているのでしょうか?
(もっともこれは自明です。元のスレッドで無いとUIのTitleプロパティなんかは変更できません。例外をスローします)
確かめてみましょう。

using System;
using System.Threading.Tasks;
using System.Windows;

namespace CSharp5Test
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            writeThreadId();
            await heavyLogicTaskAsync();
            Title = "now : " + DateTime.Now;
            writeThreadId();
        }

        private Task heavyLogicTaskAsync()
        {
            return Task.Run(() => heavyLogic());
        }

        private void heavyLogic()
        {
            writeThreadId();
            System.Threading.Thread.Sleep(3000);
        }

        private static void writeThreadId()
        {
            Console.WriteLine(
                System.Threading.Thread.CurrentThread.ManagedThreadId
                );
        }
    }
}



このプログラムは3つのスレッドIDを出力します。

1.重い処理を始める前のスレッドID
2.重い処理を行うスレッドID
3.重い処理が終わった後のスレッドID

の3つです。
IDがどうなるかは環境によるでしょうが、私の環境ではこうなりました:

9
11
9

つまり、1と3が同じスレッドで、2は別に実行されているということです。
これはやや非直観的というか魔法のようですが、便利ですね。


戻り値を返す場合

以上のサンプルでは、メソッドの戻り値はvoidでした。
しかし実際には重い処理が何か結果を返したくなることもあるでしょう。
そういう時にはこのように書きます。
Task<T>をawaitするのですね。

using System;
using System.Threading.Tasks;
using System.Windows;

namespace CSharp5Test
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            DateTime now = await heavyLogicTaskAsync();
            Title = "now : " + now;
        }

        private Task<DateTime> heavyLogicTaskAsync()
        {
            return Task.Run(() => heavyLogic());
        }

        private DateTime heavyLogic()
        {
            System.Threading.Thread.Sleep(3000);
            return DateTime.Now;
        }
    }
}

このプログラムは重い処理をして現在時刻を返すメソッドを呼びます。
ただしasyncでawaitしています。

Task<T>をawaitすると、それは代わりにTになります。
ここではTask<DateTime>をawaitしているので現在時刻の入ったDateTimeが戻ってきているのです。
nowには現在時刻が入ります。



コンソールアプリケーションでasync、await

ここまでの例はWPFを使ったものでしたが、もちろんコンソールアプリケーションでも使えます。
本来は単純なこっちを先に例として出すべきだったかもしれません。
こうなります。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AwaitConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            execute();

            Console.WriteLine("startSleeping: ");
            Thread.Sleep(3000);
        }

        private static async void execute()
        {
            await Task.Run(() => heavyLogic());
            Console.WriteLine("heavy logic completed:");
        }

        private static void heavyLogic()
        {
            Console.WriteLine("heavy logic thread:");
            Thread.Sleep(300);
        }
    }
}




実行結果はこうなります。

startSleeping:
heavy logic thread:
heavy logic completed:

どうでしょうか。
よくわかりませんね。
スレッドのIDを表示してみましょう。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AwaitConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            execute();

            Console.WriteLine("startSleeping: " + getThreadId());
            Thread.Sleep(3000);
        }

        private static async void execute()
        {
            await Task.Run(() => heavyLogic());
            Console.WriteLine("heavy logic completed:" + getThreadId());
        }

        private static void heavyLogic()
        {
            Console.WriteLine("heavy logic thread:"  + getThreadId());
            Thread.Sleep(300);
        }

        private static int getThreadId()
        {
            return Thread.CurrentThread.ManagedThreadId;
        }
    }
}


各スレッドのIDを表示するようにしました。
どうなっているでしょう?

startSleeping: 10
heavy logic thread:11
heavy logic completed:11
 
まあこんなもんでしょう。

・・・・・・

!?


どういうことでしょうかこれは!
ある意味当たり前ですが。
私はこれまでてっきりheavy logic completedのスレッドIDは常に10になると思っていました。
というかそういう解説をしてきましたしネットでもそんな解説です。
WPFの実験でもそうでした。
が、コンソールではそうなりません。
await ~~;の後のステートメントも別スレッドで実行されています。

これはコンソールではスレッドが暇になる保証がないからでしょうか?
WPFにはメッセージループがありますからね
しかしそうなら、呼び出し元がGUIかコンソールか、どうやって知るのでしょう?
うーんわかりません
自分でも何を口走っているのか分からなくなってきました。

こういうことは言いたくありませんが、まさかバグでしょうか?
(何せこれはまだDeveloper Preview版ですからね)
しかしフレームワークのバグよりもまず自分を疑えという格言もあります。

そもそもWPF版のが魔法のようだったのでこれが普通なのかもしれませんね。

というようなことを考えていると、こんなページを見つけました。
曰く:

「追記:何人かの人々は私に尋ねた。「つまりそれは、Task Asynchrony Patternがメッセージ・ループを持っているUIスレッドでだけ機能するという意味なのか?」いいえ。Task Parallel Libraryは並列性に関係する問題を解決するために明示的に設計された;タスク非同期はその仕事を拡張する。ASP.NET.のように、ユーザ・インタフェースを駆動するメッセージ・ループのないマルチスレッド環境で、非同期が動くのを許すメカニズムがある; 本稿の意図は非同期がUIスレッドでどのようにマルチスレッディングなしで機能するかについて述べることで、非同期がマルチスレッディングなしのUIスレッドだけで機能すると言うことではない。私は、後日、他の種類の「オーケストレーション」コードが、どのタスクをいつ走らせるかを解決するという、サーバー・シナリオについて話すだろう。」

だそうです。
つまり・・・どういうことでしょう??

さらにこんなページを見つけました。
このページによると、await~~の後のコードが°のスレッドで実行されるかはTaskScheduler.FromCurrentSynchronizationContext()によって決定されるようです。
これで謎が解けました。
メッセージループを持つUIなんかではこれによってUIスレッドで継続するわけですね。














拍手[6回]

PR

Visual Studio11 Developer Previewインストール失敗

Visual Studio 11 Developer Previewをインストールしようとしたところ、次のような表示が出て失敗しました。

VisualStudio11InstallationFailed.JPG


表示を読むと

Microsoft .NET Framework 4 Multi-Targeting Pack

The endpoint format is invalid.

とあります。

調べて見たところここに解決策がかいてありました。
どうやらインストーラーがWebを通してデータを取ってくるからいけないようです。
ここからダウンロードできるバージョンのインストーラーを使えばいいというわけですね。

(3つのファイルに分かれています。
全て落として、同じディレクトリにおいてください。
exeファイルを実行するとisoファイルが出来るので、DVDに焼くなりフリーのソフトをつかうなりして中にあるインストーラーを実行してください。)

さて私の場合はこれでも上手くいきませんでした。
イライラしながら数回再起動したのち、どうやらいったんアンインストールすればいいのではないかということに気がつきました。
ビンゴ!今度は上手くインストールできました。











拍手[0回]


Silverlightでニューラルネットワーク(バックプロパゲーション)

今回はニューラルネットワークです。
バックプロパゲーションという、「ある出力を出すように学習する」ネットワークです。
ここではXORを学習させることにします。

XOR

0 0 -> 0
0 1 -> 1
1 0 -> 1
1 1 -> 0


なぜXORなのか?

もともとこれは普通のシンプルなニューラルネットワークである2層の単純パーセプトロンで、XORを学習できないことから考えだされました。
単純なXORさえできないからニューラルネットワークはもうダメだ!ということが昔言われていたのです。
これを言ったのはミンスキーという人で、このおかげで研究が下火になったといいます。
しかし、3層のバックプロパゲーションで、今までうまく出来なかったXOR計算ができるようになった!
そこでバックプロパゲーションの例題としてXORを学習することが多いのです。

かつてニューラルネットワーク研究を復活させたバックプロパゲーション。
それをSilverlightで再現してみました。



左は入力、右の数字は出力です。
はじめのうちは出力の数字は適当です。
しかし右の学習ボタンを押すと、次第に出力値が補正されていき、最後にはほとんどXORのようになります。
学習が終わるとストップします。

ただ、最初の結合の重み(ランダムに割り当てられる)によっては、上手く学習が進まないこともあります。
その時はリセットボタンを押して結合荷重をリセットしてください。


おまけ

プロジェクトファイルです。

拍手[0回]


Silverlightで遺伝的アルゴリズム

Silverlightで仮想のロボットを、光を追いかけるように進化させました。



赤い丸が光で、黒い丸(に矢印がついたもの)がロボットです。
光はマウスポインタで操作することができます。

ロボットははじめのうちは光を追いかけようとはしません。
しかし世代を経るにつれてだんだんと光を追いかけるようになっていきます(数分くらいかかります)。
右上のグラフはそのスコア(を上下に反転したもの)です。

時間が経つに連れてスコアが良くなっていくのがわかると思います。
また、ロボットがより効率良くマウスポインタを追いかけるようになるのもわかると思います。

ロボットの仕組みはこうなっています。
ロボットには8つの光センサーと2つのタイヤがついています。
8つのセンサーからの出力は重み付けられた和となってタイヤに与えられます。
シンプルな構成です。

この重みをこのSilverlightでは変化させていくのです。
結果ロボットは光を追いかけるようになっていきます。

おまけ

プロジェクトファイルです。

拍手[0回]


Silverlightで『模型は心を持ちうるか』ver2

模型は心を持ちうるか』という人工知能の本があります。
私はまだろくに読んでないのですが、一部だけ見た感じだと、こんな感じのようです:

こんなロボットがあったとします。
(リンク先から引用)

Vehicle2.JPG



このロボットは本体+2つのセンサーと2つの車輪からなっています。
上にある手のように見えるものは実は手ではなく光センサーです。
そこから信号を伝える電線が下の車輪に伸びています。

このとき、電線のつなぎ方は2通りあります。
平行に繋ぐ方法とクロスさせる方法。
aとbはその2通りのつなぎ方を表しています。

実は、このつなぎ方によって振る舞いに大きな違いが生まれるのです。
というか、単にセンサーと車輪をつなぐだけでも、面白い振る舞いが生まれます。

Vehicle2Behavior.JPG

このとおり、aのようなつなぎ方をすると光を恐れるようになり、bのようなつなぎ方をすると光を追いかけるようになります。
単純な仕掛けから高等に見えるふるまいが出てくる。
人工知能のエッセンスですね。

さてこれをSilverlightで再現してみました。



このSilverlightでは光ではなくマウスポインタを追いかけます。
チェックボックスにチェックを入れると内部の配線が切り替わり、今度は逆に光から逃げるようになります。


おまけ

プロジェクトファイルです。
このようなケースでMVVMを使うのは難しいですね。











拍手[1回]