アスペクトを使った非同期実行

前に書いた[[C#でアスペクト指向](http://qiita.com/SR2200/items/0eb71126e0b5787b2d71 "C#でアスペクト指向")] 、[[アスペクトを使った再現情報取得](http://qiita.com/SR2200/items/94cc4afdeac7768f4b05 "アスペクトを使った再現情報取得")]の続きです。アスペクトを使って非同期処理を実装してみようかと。

# アスペクトで非同期処理
こんな感じで非同期実行を実装します。Message Queue（MSMQ）を使用した非同期実行です。MQにSQL Server の [Service Broker](https://msdn.microsoft.com/ja-jp/library/bb522893.aspx "Service Broker") を使ってみようと思いましたが、簡単なMSMQにしました。

![Aspect04.png](https://qiita-image-store.s3.amazonaws.com/0/125598/00462073-844c-3fe7-5d68-0633ca56a197.png)

# サンプルコード
下記のサンプルコードはものすごく簡単なものですが、例えばメール送信処理とか大量のDB更新処理とか時間がかかる処理を実行するのに最適化と思っています。

```csharp:Sampleコード
    class Program
    {
        static void Main(string[] args)
        {
            SampleClass cls = new SampleClass();
            cls.SampleMethod("A", 10);
            Console.ReadLine();
        }
    }

    [MyAsyncAspect]
    public class SampleClass : ContextBoundObject
    {
        internal int SampleMethod(string str, int count)
        {
            Console.WriteLine("exec : start");
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < count; i++)
            {
                sb.Append(str);
            }
            Console.WriteLine(sb.ToString());
            Console.WriteLine("exec : end");
            return 0;
        }
    }
```

# アスペクトの実装
アスペクトは[[C#でアスペクト指向](http://qiita.com/SR2200/items/0eb71126e0b5787b2d71 "C#でアスペクト指向")] 、[[アスペクトを使った再現情報取得](http://qiita.com/SR2200/items/94cc4afdeac7768f4b05 "アスペクトを使った再現情報取得")]で簡単に説明しているはずなので、説明は抜きで早速コードを。

```csharp:MyAsyncAspectAttributeクラス
    public class MyAsyncAspectAttribute : ProxyAttribute
    {
        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            MarshalByRefObject target = base.CreateInstance(serverType);
            RealProxy rp = new MyAsyncProxy(target, serverType);
            return rp.GetTransparentProxy() as MarshalByRefObject;
        }
    }
```

ProxyクラスでMQにMessageをSendします。が、ここで注意事項。実行するのかMQにSendするのかを判断する必要があります。なので、非同期実行機能側のApp.configに設定値（ASYNC_EXEC_FLAG）を持たせ、この値が「EXECUTE」の場合に処理を実行するようにしています。

```csharp:MyAsyncProxyクラス
    public class MyAsyncProxy : RealProxy
    {
        private MarshalByRefObject _target;
        private const string MQ_NAME = @".\private$\MyQueue";

        public MyAsyncProxy(MarshalByRefObject target, Type t) : base(t)
        {
            this._target = target;
        }

        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage call = (IMethodCallMessage)msg;
            IMethodReturnMessage res;
            IConstructionCallMessage ctor = call as IConstructionCallMessage;

            if (ctor != null)
            {
                //以下、コンストラクタを実行する処理

                RealProxy rp = RemotingServices.GetRealProxy(this._target);
                res = rp.InitializeServerObject(ctor);
                MarshalByRefObject tp = this.GetTransparentProxy() as MarshalByRefObject;
                res = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
            }
            else
            {
                //以下、コンストラクタ以外のメソッドを実行する処理

                string execFlag = ConfigurationManager.AppSettings["ASYNC_EXEC_FLAG"];
                if(execFlag == "EXECUTE")
                {
                    //メソッド実行
                    res = RemotingServices.ExecuteMessage(this._target, call);
                }
                else
                {
                    //メソッド前処理
                    Console.WriteLine("非同期キューにSEND：start");
                    SendAsyncQueue(call);
                    Console.WriteLine("非同期キューにSEND：end");

                    //メソッド実行
                    res = new ReturnMessage(0, null, 0, null, call);
                }
            }

            return res;
        }

        private void SendAsyncQueue(IMethodCallMessage call)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(stream, call);

                Message message = new Message(stream.ToArray(), 
                                              new BinaryMessageFormatter());

                MessageQueue queue = new MessageQueue(MQ_NAME);
                queue.Send(message);
            }
        }
    }
```

# 非同期実行機能の実装
こちらの実装も[[アスペクトを使った再現情報取得](http://qiita.com/SR2200/items/94cc4afdeac7768f4b05 "アスペクトを使った再現情報取得")]のように実行していきます。[[アスペクトを使った再現情報取得](http://qiita.com/SR2200/items/94cc4afdeac7768f4b05 "アスペクトを使った再現情報取得")]ではファイルに出力した内容を読み込み処理を実行していますが、今回はMSMQにSendされたメッセージを取得し、実行しています。

```csharp:非同期実行機能
    class Program
    {
        static void Main(string[] args)
        {
            Program prg = new Program();
            prg.Execute(args);

            Console.ReadLine();
        }

        private const string MQ_NAME = @".\private$\MyQueue";

        private string baseDir;

        private void Execute(string[] args)
        {
            baseDir = Environment.CurrentDirectory;
            Receive();
        }

        private static void Receive()
        {
            MessageQueue queue = new MessageQueue(MQ_NAME);
            Message msg = queue.Receive();
            msg.Formatter = new BinaryMessageFormatter();

            byte[] asyncData = (byte[])msg.Body;

            IMethodCallMessage callMsg = null;

            using (MemoryStream stream = new MemoryStream(asyncData))
            {
                BinaryFormatter bf = new BinaryFormatter();
                callMsg = (IMethodCallMessage)bf.Deserialize(stream);
            }

            Type typ = Type.GetType(callMsg.TypeName);
            object target = Activator.CreateInstance(typ);

            callMsg.MethodBase.Invoke(target, callMsg.Args);
        }

        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs eArgs)
        {
            string asmPath = Path.Combine(baseDir, eArgs.Name.Split(',')[0] + ".dll");
            if (File.Exists(asmPath))
            {
                Assembly asm = Assembly.LoadFrom(asmPath);
                return asm;
            }

            asmPath = Path.Combine(baseDir, eArgs.Name.Split(',')[0] + ".exe");
            if (File.Exists(asmPath))
            {
                Assembly asm = Assembly.LoadFrom(asmPath);
                return asm;
            }

            return null;
        }
    }
```

# 実行結果
実行結果は下図のようになります。
アプリ側はMSMQにSendし、処理終了。非同期実行側はMSMQからReceiveし対象のロジックを実行しています。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/125598/e216e004-ec8a-6c57-909d-0e5b945d703b.png)

![キャプチャ２.PNG](https://qiita-image-store.s3.amazonaws.com/0/125598/1b5372ae-8c92-b6a7-91cc-4f48dc42887a.png)