요즘 참 할일도 많은데 할 수 있는 일이 점점 줄어든다. 필자는 블로그 버킷 리스트(bucket list)를 작성하는데 블로그가 사망하기 전에 꼭 해야 할 일을 목록으로 만들어 놓고 하나 하나씩 글을 써 나간다. 근데 할 일이 늘어만 간다. ㅠ

  • 당장 쓸 수 있는 글 39개
    사소한 개발 기술부터 심도있는 내용으로 흐리멍텅한 개념을 글을 쓰면서 잡아 나가는 것들

  • 개발 후 산출물로 쓸 글 37개
    오픈소스로 내놓을 계획, 또는 알고 있는 것들에 대한 증명이 필요하고 그 후에 쓸 수 있는 글

  • 연구개발 11개
    배우고 싶은 것, 하고 싶은 것, 해야 하는 것들이고 공부해야 쓸 수 있는 글들

아무튼 점점 쓸 것들이 늘어만 가지만, 하나 하나 하다보면 쓸게 없어 지는 날이 올거라 믿는다 >.,<

#1 - Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy)

다이나믹 프록시(Dynamic Proxy)는 최근 IoC(Inversion of Controls)라는 개념으로 확장되고 객체지향 프로그래밍(OOP)의 효율성을 극대화한다. 다이나믹 프록시 기법은 이미 오래 전부터 존재해 왔다.

C++의 스마트 포인터(Smart Pointer) 처럼 직접 포인터를 건들이지 않고 레퍼런스 카운팅이나 객체의 생명주기를 프록시 개체(Proxy Object)를 통해 관리하는 기법이 있고, RPC(Remote Procedure Call)과 같은 원격 프로시저 호출을 위해 프록시 패턴(Proxy Pattern)이 기반에 깔려 직접 원격 구현을 숨기고 간단한 인터페이스만 제공해 주는 패턴 등 매우 다양하다.

최근의 다이나믹 프록시(Dynamic Proxy)는 IoC(Inversion of Control), DI(Dependency Injection), ORM(Object Relation Mapping), AOP(Aspect Oriented Programming) 등 객체를 다루는 프레임워크에서 기본적으로 채용할 만큼 객체를 다룸에 있어 가장 기본적인 기법 또는 기술에 속한다.

필자가 오늘 다루어 볼 내용은 이런 프록시 패턴(Proxy Pattern)이 기반이 되는 개체 제어를 위한 기법이다.

먼저 다이나믹 프록시(Dynamic Proxy)에 대해 예전에 잠깐 쓴 글을 참고하자.

과거 2009년도 닷넷엑스퍼트 재직 시절에 다이나믹 프록시(Dynamic Proxy)를 만들었다가, 2011년 좀 더 나은 객체 디자인으로 완전히 다시 만든 다이나믹 프록시(Dynamic Proxy) 프레임워크가 지금 설명할 Umc.Core.Dynamic.Proxy 이다.

Umc Core 프레임워크는 오픈 소스로 공개되어 있으며 누구든지 소스 코드를 열람할 수 있다. 현재 소스 코드는 다음의 링크를 통해 제공된다.

그 외에 필자가 공개한 소스 코드도 여럿 있으니 관심있는 분들은 참고하기 바란다.

 

 







#2 - 코드부터 보자.

만약 관리 언어(Managed Language)의 동작 매커니즘을 이해하지 못했다면 마치 마법과도 같은 기법으로 보일 것이다. 아래의 코드는 완벽하게 돌아가는 소스 코드이다.

using System;  
using Umc.Core.Dynamic;  

namespace ConsoleApplication1
{  
public interface IPerson
{
    string Name { get; set; }
    string Email { get; set; }
}  

class Program
{
    static void Main(string[] args)
    {
        var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();
        var person = (IPerson)Activator.CreateInstance(type);

        person.Name  = "POWERUMC";
        person.Email = "powerumc at 지멜";

        Console.WriteLine("Name : {0}, Email : {1}", person.Name, person.Email);
    }
}
}  

한번 위의 코드를 보자. IPerson 인터페이스는 IPerson을 구현하는 클래스가 반드시 있어야 객체를 생성할 수 있다. 다음과 같이 말이다.

public class Person : IPerson
{
    public string Name { get; set; }
    public string Email { get; set; }
}   

C#의 상식으로 보아도 인터페이스(Interface)는 인스턴스를 생성할 수 없으며, 인스턴스를 생성하기 위해 구현 클래스가 반드시 존재햐여 하는 것 쯤은 알고 있을 것이다. 즉, Person 클래스가 있어야 IPerson person = new Person(); 으로 객체를 생성할 수 있다. 분명, 처음 코드의 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 가 뭔가의 마법을 부리는 것이 확실하다고 보면 된다.

#3 - 다이나믹 프록시(Dynamic Proxy) 의 다른 구현 방법들

방금 언급한 DynamicProxyObject.InterfaceImplementationType<IPerson>(); 코드가 바로 class 로 구현조차 되지 않은 클래스를 동적(Dynamic)으로 런타임에 생성한다. 이 동적 클래스를 런타임에 생성하기 가장 쉬운 방법이 있는데, 그것은 .NET Framework에서 제공하는 CodeDomProvider 이다.

간단히 CodeDomProvider를 언급만 하고 넘어가보자. CodeDomProvider는 C#, VB.NET의 객체 표현을 Code Dom으로 표현할 수 있는데, 각각 CSharp, VB.NET Provider를 제공해 주고, 런타임에 컴파일을 한다. 다만, 컴파일 시간이 동적 객체 생성 기법 중에 가장 느리다.

그리고 C# 3.0부터 지원하는 람다 표현식(Lambda Expression)을 이용하는 방법이다. 이 람다 표현식도 내부적으로 CodeDom과 매우 유사하다. 단점이라면, 객체의 이름(Class Name)과 주소(Namespace) 등을 원하는데로 줄 수 없다. LambdaExpression 클래스의 맴버들이 많은데, 이것들로 모든 구현이 가능하다. 간단한 예제 코드를 보자.

Expression<Func<string, string>> expression = (s) => "Hello " + s;               
var func = expression.Compile();               
var ret = func("POWERUMC");   
Console.WriteLine(ret);

이 코드를 실행하면 “Hello POWERUMC” 라는 결과가 나오는 것을 확인할 수 있다.

이렇게 알게 모르게 이미 .NET Framework에서 다이나믹 프록시(Dynamic Proxy) 기법을 쓰는 곳이 의외로 많다. C# 컴파일러는 익명의 표현식들을 내부적으로 컴파일하면서 메서드로 만들어 버리기도 한다. 이렇게 똑똑한 컴파일러 덕분에 람다 표현식이나 LINQ의 성능이 매우 좋다는 것이다.

이 외에 동적메서드(DynamicMethod) 방법이 있는데, 얘는 그냥 넘어가도 무관하다.

#4 - 다이나믹 프록시(Dynamic Proxy) 개체가 생성되는 일련의 순서

동적 개체가 만들어지려면 어떤 순서가 필요할까?

먼저 객체지향 언어라는 점을 생각해볼때, 객체의 주소(Namespace)와 이름(Type Name)이 필요하다. 필자의 프레임워크 코드 중 Umc.Core.Dynamic.Proxy.Builder 네임스페이스의 ModuleBuilderExtension와 TypeBuilderExtension이 그 역할을 한다.

Type을 생성하기 위해서는 생성자 하나 이상이 반드시 필요한데, Object를 상속하는 타입을 만들어야 한다. 아시다시피 public class Person 클래스는 암묵적으로 public class Person : Object 를 상속하게 된다. 그래서 타입을 생성하고 하나의 생성자를 만들고 나면, 부모 객체인 Object의 생성자(Constructor)를 호출을 해주어야 한다. 당연히 부모가 있어야 자식이 있는 것과 같다.

이 때 반드시 주의해야 하는 것이 있다. 생성자는 인스턴스를 생성하는 생성자가 있고, static 생성자가 있다. 만약 static 생성자인 경우는 부모 객체인 Object 생성자(Constructor)를 호출하면 안된다.

Object 생성자를 타입의 생성자 안에서 호출하려면 다음과 같은 코드를 이용하면 된다.

il.Emit(OpCodes.Ldarg_0);  
l.Emit(OpCodes.Call, typeof(object).GetConstructors([0\]);  

이제 IPerson의 프로퍼티(Property)를 구현한다.

IPerson의 프로퍼티 목록은 리플랙션(Reflection)을 이용하여 쉽게 얻어낼 수 있다. 간단히 typeof(IPerson).GetProperties(); 면 된다. (프레임워크 내부에선 더 복잡할 수 있음)

C#의 프로퍼티는 컴파일을 하게 되면 프로퍼티가 메서드로 치환된다. 예를 들어, public string Name { get; set; } 코드는 다음 처럼 컴파일러가 치환한다.

public string get_Name() { ... }  
public void set_Name(string name) { ... }  

그러므로, 다이나믹 프록시를 구현할 때도 프로퍼티를 선언해 준 후에 get, set 메서드를 구현해 주어야 한다. 이를 Umc.Core에서 다음고 같이 구현하였다.

public ICodeLambda Get()
{  
var type       = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  
var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, propertyBuilder.PropertyType, String.Concat("get_", propertyBuilder.Name), Type.EmptyTypes, null, false);  

propertyBuilder.SetGetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());
}  

public ICodeLambda Set()
{  
var type = new TypeBuilderExtension(null, this.TypeLambda.TypeBuilder);  

var methodAttr = this.TypeLambda.MethodAccessor.MethodAttribute | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig;  
var method     = type.CreateMethod(methodAttr, typeof(void), String.Concat("set_", propertyBuilder.Name), new Type[] { propertyBuilder.PropertyType }, null, false);  
method.DefineParameter(1, ParameterAttributes.HasDefault, "value");  

propertyBuilder.SetSetMethod(method);  

return new CodeLambda(this.TypeLambda, method.GetILGenerator());

}  

그리고 get, set 메서드의 내부 코드는 다음과 같이 구현된다.

public ITypeLambda GetSet()
{  
this.TypeLambda.FieldAccessor.FieldAttribute = FieldAttributes.Private;  
var field = this.TypeLambda.Field(this.propertyBuilder.PropertyType, String.Concat("__", propertyBuilder.Name));  

var get = this.Get();
{
    get.Return(field);
}  

var set = this.Set();
{
    set.IL.Emit(OpCodes.Ldarg_0);
    set.IL.Emit(OpCodes.Ldarg_1);
    set.IL.Emit(OpCodes.Stfld, ( (IValuable<FieldBuilder>)field ).Value);

    set.Return();
}  

return this.TypeLambda;
}  

위의 코드가 그 흔한 프로퍼티의 구현부인 public string Name { get { return this._name; } set { this._name = value; } } 를 구현하는 코드가 되겠다. 별거 아닌 코드지만 내부적으로 프로퍼티와 메서드를 생성하고, 그 구현 코드를 IL 코드로 재구현 하면 역시 조금 난이도가 높아진다.

메모리에서 개체 포인터의 엑세스를 방지하기 완료 방법

지금까지는 메모리에 동적인 코드들을 IL 코드들로 구현하였다. 모든 구현이 완료되었으면 더 이상 메모리에서 IL 코드가 위변조 되거나 .NET 코드 정책에 위배되지 않도록 엑세스 접근을 막아야 한다.

이 코드는 Umc.Core.Dynamic.Proxy.Lambda.TypeLambda 클래스의 CreateType() 메서드가 완료시킨다. 이 코드는 다음과 같이 구현 되었다.

public Type ReleaseType()
{  
if (this.TypeBuilder == null)
{
    throw new NullReferenceException(this.TypeBuilder.GetType().Name);
}  

return this.TypeBuilder.CreateType();
}   

코드 구현의 완료를 알렸다면 이제 동적 타입(Dynamic Type) 개체가 완성이 된다. 즉, 맨 처럼 IPerson 인터페이스를 구현하는 동적 개체(Dynamic Object)인 Person 개체의 타입이 되는 것이다. 즉, 이 Person 개체는 런타임에서 메모리상에 만들어진 것이 되겠다.

이리하여 Activator.CreateInstance(type) 으로 메모리상에 만들어진 Person 클래스를 생성할 수 있게 되는 것이다.

다이나믹 프록시(Dynamic Proxy) 개체의 모습

메모리상에서 만들어진 Person 개체의 타입은 내부적으로 규칙을 정해서 만들었다. Person 객체를 생성하여 이 객체의 타입을 보면 괴상한 이름으로 지어졌다.

var type   = DynamicProxyObject.InterfaceImplementationType<IPerson>();  
var person = (IPerson)Activator.CreateInstance(type);  

// 결과 -> dynamic_46c8ecaab09b41cbaa814511f69f1974

이 타입이 속한 주소(Namespace)와 모듈(Module)을 직접 디스크에 저장할 수도 있는데, 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 처럼 사용할 수도 있다.

또 다이나믹 프록시(Dynamic Proxy)를 활용해 중간에 로깅(Logging)을 하거나 트랜잭션(Transaction) 처리를 할 수 있는 AOP 를 구현할 수도 있는 것이다. (AOP 구현 기법 중 하나이며, 다른 방법이 더 있다.)

또, 사용 편의성을 위해 고안된 것이 체이닝(Chaining) 방법을 이용하여 런타임 코드를 더 쉽게 만드는 방법을 Umc Core 프레임워크에서 제공한다. 다음의 코드를 보면 쉽게 이해가 될 것이다.

using System;  
using System.Linq;  
using System.Reflection;  
using Umc.Core.Dynamic.Proxy.Lambda;  

namespace ConsoleApplication1
{  
internal class Program
{
    private static void Main(string[] args)
    {
        string typeName = Guid.NewGuid().ToString("N");
        string methodName = Guid.NewGuid().ToString("N");

        var assembly = new AssemblyLambda().Assembly();
        {
            var module = assembly.Module();
            {
                var type = module.Public.Class(typeName);
                {
                    var constructor1 = type.Public.Constructor();
                    {
                        constructor1.Emit.EmitWriteLine("This is constructor");
                        constructor1.Return();
                    }

                    var constructor2 = type.Private.Static.Constructor();
                    {
                        constructor2.Emit.EmitWriteLine("This is (private static) constructor");
                        constructor2.Return();
                    }

                    var method = type.Public.Static.Method(methodName);
                    {
                        method.Emit.EmitWriteLine("This is emitted writeline");
                        method.Return();
                    }
                }

                var releaseType = type.ReleaseType();
                var obj = System.Activator.CreateInstance(releaseType);
                var releaseMethod = releaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);

                Console.WriteLine("Release type is {0}", releaseType.AssemblyQualifiedName);
                Console.WriteLine("Release method is {0}", releaseMethod.Name);

                releaseType.GetMethod(methodName).Invoke(null, null);

                Console.WriteLine("".PadLeft(50, '-'));

                releaseType.GetMethods().ToList().ForEach(m => Console.WriteLine("Method Name is " + m.Name));
            }
        }
    }
}
}



// 결과 ->  
This is (private static) constructor  
This is constructor  
Release type is bdfd27e5e7ed446d8702fe172733d937, 4a8f6dd24e1f4054b551cca95ad0870b, Version=0.0.0.0, Culture=neutral,  PublicKeyToken=null  
Release method is 389b4a43a1ba4dc1b1391b5b06633645  
This is emitted writeline  
Method Name is 389b4a43a1ba4dc1b1391b5b06633645  
Method Name is ToString  
Method Name is Equals  
Method Name is GetHashCode  
Method Name is GetType  

위의 코드와 같이 좀 더 직관적으로 동적 코드를 만드는 방법을 Umc Core에서는 제공해 주고 있다.

재미있는 것은 메서드의 이름이다. 결과 출력 중 Method Name is 389b4a43a1ba4dc1b1391b5b06633645 이 있는데, 놀랍게도 메서드의 이름이 숫자로 시작한다. C# 언어 사양 중 변수의 타입, 변수 등의 이름은 반드시 알파벳 문자로 시작해야 함에도 불구하고, 숫자로 시작하다니…

이는 C#의 언어 사양과 CIL(Common Intermediate Language) 언어의 사양이 다르기 때문이다. 심지어 숫자가 아닌 특수문자로 시작해도 된다. 앞서 말했다시피 이런 방법을 이용하여 코드 난독화(Code Obfuscation) 가 가능하다.

비록 대부분의 Umc Core 소스 코드를 공개하지 않았지만, 미공개 코드 중 코드 난독화 툴도 함께 포함이 되어있고, Junk Assembly를 만드는 툴, AOP based Compile 툴 등도 포함이 되어있다. ㅎㅎㅎ; 아쉽지만 여전히 미공개 분은 공개하지 않을 예정이다. 더불어, 관심있는 독자라면 CIL 언어 스팩의 사양을 살펴보는 것도 좋을 것이다.

결론

지금까지 살펴본 다이나믹 프록시(Dynamic Proxy)를 살펴보았다. 이 기법은 IoC, DI, AOP, ORM 등 최신 객체 제어 기술들의 가장 근간이 되는 기법이다. 여러분들이 최신 기술들로 IoC, DI, AOP 등을 구현하고 싶다면 당장 오픈소스를 이용하면 될 것이다.

아마 필자처럼 .NET 기반으로 다이나믹 프록시(Dynamic Proxy)를 직접 구현해서 쓰는 것은 매우 드문 일이다. 이미 검증된 오픈 소스들도 많기 때문에 굳이 이를 구현해서 쓸 필요는 없다.

하지만 이런 오픈 소스를 이용하여 쓰는 것은 그리 어렵지 않겠지만, 이런 오픈 소스를 이용하여 어떻게 코드를 만드느냐는 성능이나 내부적으로 동작하는 효율성에 영향을 미칠 수 있다. 즉, 자신이 오픈 소스를 이용하여 만든 코드가 효율적인 코드, 그리고 메모리상에서 Clean Code로 동작하느냐는 별개의 문제이다.

필자와 같이 다이나믹 프록시(Dynamic Proxy)를 구현해 보았다면 여러분들이 얼마나 위험한 코드를 많이 만드는지 알 수 있다. 즉, 오픈 소스를 써보기만 했다면 절대로 알 수 없는 고급 기술이다.

ASP.NET MVC 등과 같은 프레임워크에 IoC 등이 통합되면서 내부적으로 이와 유사한 방법을 쓴다. 특히 웹 개발에 있어 IoC와 같은 프레임워크는 굉장히 위험할 수 있다. 설명하자면 지금까지 쓴 내용보다 더 복잡하고 많은 내용으로 설명이 필요할지도 모른다.

만약 관리 언어(Managed Language) 등으로 만들어진(자바도 구현 기법이 유사함) 응용 프로그램의 고성능 튜닝, 트러블 슈팅, .NET 플랫폼에 관심이 많은 독자라면 반드시 이해하고 넘어가야 할 내용들이다. 그리고 오픈 소스를 이용하여 다이나믹 프록시(Dynamic Proxy)를 간접적으로 사용만 한다면 가볍게 읽고 넘어가도 좋다. 소소한 개발 일상의 이야깃 거리도 될 것이다.


필자는 이 외에도 다이나믹 프록시(Dynamic Proxy)를 바탕으로 이를 IoC와 통합하고 AOP, ORM 등을 구현하는 방법도 알아볼 것이다. Umc Core는 오픈 소스로 공개되어 있으며 위키 페이지를 만들겸 하나씩 정리하고자 한다. 

'Umc Projects > Umc.Core' 카테고리의 다른 글

Umc Core IoC 통합 컨테이너 #1  (0) 2013.05.24
Umc.Core 프레임워크 다이나믹 프록시(Dynamic Proxy) #1  (0) 2013.05.23
Umc.Core 미공개 Preview  (6) 2008.05.14
Umc.Core 란?  (0) 2007.12.01
Posted by 땡초 POWERUMC

댓글을 달아 주세요

혹시 위의 CMD 에서 키보드를 잘못 누른경우 아래의 인터넷 옵션의 인증서 창에서 신뢰되지 않은 게시자를 지우기 바랍니다.

캐시 서버 시작

   

콘솔 프로젝트에 참조 추가

CTP2 에서 System.Data.Caching 이 Microsoft.Data.Caching 으로 네임스페이스가 변경되었습니다.

   

NamedCache 를 만듭니다.

New-Cache -CacheName NamedCache1 -Secondaries 1 -TTL 15

   

App.Config 의 설정입니다.

App.config

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

   

<!--configSections must be the FIRST element -->

<configSections>

   

<!-- required to read the <dataCacheClient> element -->

<section name="dataCacheClient"

type="Microsoft.Data.Caching.DataCacheClientSection,

CacheBaseLibrary"

allowLocation="true"

allowDefinition="Everywhere"/>

   

<!-- required to read the <fabric> element, when present -->

<section name="fabric"

type="System.Data.Fabric.Common.ConfigFile,

FabricCommon"

allowLocation="true"

allowDefinition="Everywhere"/>

   

</configSections>

   

<!-- simple client -->

<dataCacheClient deployment="simple">

   

<!-- (optional) specify local cache

<localCache

isEnabled="true"

sync="TTLBased"

objectCount="100000"

ttlValue="300" />

-->

   

<!-- note: cache notifications are

not supported with simple client -->

   

<!-- cache host(s) -->

<hosts>

<host

name="DPOWERUMC"

cachePort="22233"

cacheHostName="DistributedCacheService"/>

</hosts>

</dataCacheClient>

</configuration>

   

아래는 간단한 샘플 소스 코드입니다.

Program.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

   

using Microsoft.Data.Caching;

   

namespace ConsoleApplication1

{

class Program

{

static void Main(string[] args)

{

//declare array for cache host(s)

DataCacheServerEndpoint[] servers = new DataCacheServerEndpoint[1];

   

//specify cache host(s)

servers[0] = new DataCacheServerEndpoint("DPOWERUMC",

22233, "DistributedCacheService");

   

//specify cache client configuration

DataCacheFactory mycacheFactory

= new DataCacheFactory(servers, true, true);

   

   

//get cache client for cache "NamedCache1"

DataCache myDefaultCache = mycacheFactory.GetCache("NamedCache1");

   

   

   

}

}

}

   

   

아래는 캐시 서버를 중지하는 방법입니다.

   

   

그리고 ShareFolder 로 설치하면 Compact 3.5 Database 인 .SDF 파일이 생깁니다. 캐시 서버가 동작할 경우 파일 공유가 불가능하여 데이터베이스의 내용을 볼 수 없습니다. 필자의 견해로는 Compact DB 가 아닌 SQL Server Database 로 설치할 것을 권장합니다.

 

설치를 수정하려면 C:\Program Files\Microsoft Distributed Cache\V1.0\Uninstall-128913592820152769.exe 를 실행하시면 됩니다. 단, 프로그램 추가/제거에서 찾을 수 없답니다. ^^

Posted by 땡초 POWERUMC

댓글을 달아 주세요

C:\Program Files\Microsoft Distributed Cache\V1.0 폴더를 모두 새로운 클러스터 서비스의 폴더로 이동합니다.

필자는 C:\VELOCITY\DPOWERUMC_CLUSTOR01 요기에다가 이동했습니다.

단, 등호와 값 사이에는 공백이 한 칸 있어야 한다. -_-; (잘보세요… 그렇지 않으면 오류가 납니다 ^^;)

클러스터링 할 데이터베이스도 만들 수 있습니다.

   

그리고 DistributeCache.exe.config 에서 아래의 항목을 수정하시면 됩니다.
cacheHostName 속성
Log location 속성의 경로
DB Connection 정보


필자는 로컬 머신에 클러스터를 추가했습니다. 아래와 같이 서비스 항목에 추가된 호스트가 등록이 되었고, 서비스를 시작해 주시면 됩니다.

   

Posted by 땡초 POWERUMC

댓글을 달아 주세요

먼저 이전 포스트의 "MEF 는 Generic Type 을 지원하지 않는다!" 에서 언급했고, .NET CLR 2.0 부터 Generic Type 을 지원함에도 불구하고, .NET Framework 4.0 에 포함되는 MEF 가 Generic Type 을 지원하지 않는다는 것은 솔직히 납득하기가 어렵습니다. MEF 개발 PM 이 말하는 강력한 계약 기반(Strongly Contract Based) 의 모델이라는 점은 머리로는 이해는 되지만, 사실 안될 것도 없습니다. -_-;

MEF 가 갖는 대표적인 키워드인 Composable 은 현재 Generic Type 을 지원하지 않지만, 상당히 매력이 있습니다. 이미 현대적인 프레임워크는 Modular 에 집중하고 있고, MEF 는 더 나아가 Modular + Composite 이라는 상당한 매력을 가진 프레임워크입니다.

일단 서두는 이쯤에서 접어두고, MEF 가 Generic Type 을 지원하기 위한 몇 가지 공개되어 있는 방법을 알아보고, 다시 이야기를 나누어 봅시다. 
   

How to support Generic Type of MEF ?    

첫 번째 방법 - Factory Provider

가장 간단한 방법이 바로 Factory Pattern 을 이용한 방식입니다. 객체의 생성은 Factory 를 통해 생성하도록 하고, Factory 는 객체의 Type 을 받음으로써 객체의 생성을 Factory 에게 모두 의존하는 방법입니다. 우선 아래의 링크를 참고하세요.

MEF + Factories Using an Export Provider
http://blog.eworldui.net/post/2008/11/MEF-2b-Factories-Using-an-Export-Provider.aspx

 ExportProvider 를 재정의하여 객체의 Type 을 등록하여 원하는 Type 의 객체를 생성하도록 합니다.

1: public interface IService { }

2: public interface IUserService : IService { }

3:  

4: [Export]

5: public class UserController {

6: [ImportingConstructor]

7: public UserController(IUserService userService) { }

8: }

9:  

10: // in your application

11: private void Compose() {

12: var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());

13: var factoryProvider = new FactoryExportProvider<IService>(GetService);

14: var container = new CompositionContainer(catalog, factoryProvider);

15: container.AddPart(this);

16: container.Compose();

17: }

18:  

19: public IService GetService(Type type) { return ... }

   

하지만, 이 방법은 상당히 문제가 많은 방법입니다. 가장 즐겨쓰고, 흔히 볼 수 있는 Pattern 이기 때문에 추가되는 Factory 마다 객체 등을 Factory Provider 에 등록을 해 주어야 합니다. 그 뿐만이 아니죠. Factory Pattern 의 특성상 객체를 생성하는 Factory 는 일일이 각 객체의 타입을 체크하여 반환해 주어야 합니다.

그리고 위의 코드에서는 Type 인자가 1개이지만, 그 이상이라면??? 가령, Generic Type Class<T1,T2,T3,T4,T5> 가 된다면 대략 난감하겠죠. 일단 작은 코드에서는 쓸만할 수 있지만, 꾸준히 성장하는 코드라면 이러한 Factory 방식은 코드의 변경이 너무 잦아집니다.

   

두 번째 방법 - Type Mapping

MEF 는 Codeplex 에 공개가 되어있고, MEF Contrib 으로 불리우는 MEF 의 확장 라이브러리 입니다. MEF Contrib 의 가장 큰 특징 중에 하나인 ComposablePartCatalog 를 재정의 하는 Generic Catalog 를 지원해 줍니다. 이 링크에서 Type Mapping 을 통한 문서를 볼 수 있습니다.

public class GenericCatalogContext
{
protected AggregateCatalog _aggegateCatalog;
protected GenericCatalog _genericCatalog;
protected ImportDefinition _repositoryImportDefinition;

public GenericCatalogContext()
{
var typeCatalog = new TypeCatalog(typeof(OrderProcessor), typeof(RepositoryTypeLocator));
_aggegateCatalog =
new AggregateCatalog();
_aggegateCatalog.Catalogs.Add(typeCatalog);
_genericCatalog =
new GenericCatalog(_aggegateCatalog);
string orderProcessorContract = AttributedModelServices.GetContractName(typeof(OrderProcessor));
var orderProcessPartDefinition = typeCatalog.Parts.Single(p => p.ExportDefinitions.Any(d => d.ContractName == orderProcessorContract));
_repositoryImportDefinition = orderProcessPartDefinition.ImportDefinitions.First();
Context();
}

public virtual void Context()
{

}
}

[InheritedExport]
public abstract class GenericContractTypeMapping
{
public GenericContractTypeMapping(Type genericContractTypeDefinition, Type genericImplementationTypeDefinition)
{
}

public Type GenericContractTypeDefinition { get; }
public Type GenericImplementationTypeDefinition { get; }
}

public class RepositoryTypeLocator : GenericContractTypeMapping
{
public RepositoryTypeLocator()
:
base(typeof(IRepository<>), typeof(Repository<>))
{
}
}

public class Repository<T> : IRepository<T>
{
}

이러이러한 과정을 통해서 아래와 같이 Type Mapping 을 통해 Generic Type 을 사용할 수 있습니다.

[TestFixture]
public class When_querying_catalog_for_an_order_repository_and_no_closed_repository_is_present : GenericCatalogContext
{
[Test]
public void order_repository_part_definition_is_created()
{
Assert.IsNotNull(_result.Item1);
}

[Test]
public void order_repository_export_is_created()
{
Assert.IsNotNull(_result.Item2);
}

public override void Context()
{
_result = _genericCatalog.GetExports(_repositoryImportDefinition).Single();
}

private Tuple<ComposablePartDefinition, ExportDefinition> _result;
}

Contract Type 와 Mapping Type 을 매핑하여 Locator 로 등록하여 주고, 각각 Mapping Class 를 통해 실제 계약의 Generic Type 매핑이 이루어 집니다.

다시 말해서, Generic Class 별로 Locator Class, Mapping Class, 그리고 Mapping Context Class 를 만들어주어야 합니다. 배보다 배꼽이 더 커지는 격입니다. 일단, 아이디어는 좋지만 안쓰고 말랍니다.

   

세 번째 방법 - MEF + Unity 조합

아마도 가장 이상적인 방법이긴 합니다. Unity Application Block 은 Unity Container Extension 을 지원하기 때문에 객체의 Register, Resolve 등의 이벤트를 가로채서 Unity 의 기능을 확장할 수 있습니다. 이 이벤트를 MEF 에서 받도록 하여 MEF 의 ExportProvider 의 GetExportsCore 를 통해 Unity 의 객체에서 Resolve 하도록 하는 방법입니다.

UnityContainerExtension 을 재정의하여, 아래와 같이 이벤트를 받고, 이것을 MEF ExportProvider 로 전달하는 방법입니다.

UnityContainerExntension 에서는 아래와 같이...

protected override void Initialize()
{
this.Context.Registering += new EventHandler<RegisterEventArgs>(Context_Registering);
this.Context.RegisteringInstance += new EventHandler<RegisterInstanceEventArgs>(Context_RegisteringInstance);
}

MEF 의 ExportProvider 에서는 아래와 같이…

protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
{
if (definition.ContractName != null)
{
Type contractType;
if(Mapping.TryGetValue(definition.ContractName, out contractType))
{
if (definition.Cardinality == ImportCardinality.ExactlyOne || definition.Cardinality == ImportCardinality.ExactlyOne)
{
var export = new Export(definition.ContractName, () => serviceLocator.GetInstance(contractType));
return new List<Export> { export };
}

}
}
return Enumerable.Empty<Export>();
}

일단 가장 완벽해 보입니다만, 이 속에는 그 이상 많은 문제들이 생기게 됩니다. MEF 도 내부적으로 Injection(주입) 기법을 사용하고, Unity 에서도 Injection 을 사용하는데 바로 이 Injection 방법이 달라지게 되는 것입니다. 즉, MEF 기반의 코드와 Unity 기반의 코드의 Injection 선언 방법이 틀려지고, 서로 호환할 수 없다는 것입니다.

결국 DI 프레임워크는 특정 DI Container 에 의존할 수 밖에 없어지고, 더불어 Compisite 과 Injection 은 두 가지의 사용 방법이 혼재될 수 밖에 없다는 것이죠.

   

Conclusion

MEF 에서 Generic Type 을 사용하고 싶어서 안달이 난 1은 여러 가지 방법을 찾아보았지만, 사용성, 재사용성, 확장성, 유연성 등 모든 면에서 원하는 해답을 찾지 못했습니다. 그리고 현재까지 MEF 에서 Generic Type 을 지원하기 위한 대략적인 3가지 방법을 정리해보도록 하죠.

  

장점

단점

MEF Factory Export Provider

  • 구현이 쉽다
  • Factory 의 관리가 힘들다
  • Factory 의 확장이 힘들다
  • 모든 Factory 를 Catalog 로 관리해야 한다.

MEF Contrib Type Mapping

  • 합리적이다
  • Type Mapping 코드가 복잡하다
  • Mapping/Locator/Context 클래스를 구현해야 한다
  • 상속 기반이다

MEF + Unity Integrated

  • 합리적이고 , 구현이 쉽다
  • Injection 기법이 서로 달라진다
  • Injection 코드가 서로 달라진다
  • Injection 이 호환되지 않는다
  • 각각의 객체간의 Composite 이 불가능하다

이제 슬슬 머리가 아파옵니다. 향후 .NET Framework 4.0 에서 가장 큰 빛을 보게 될 MEF 이지만, Generic Type 을 지원하지 않는다는 것은 가장 큰 오점이 아닐까 생각합니다. 우선 이쯤에서 마무리하고 어떻게 해야 할지 생각해 보도록 하지요.


MEF 에서 Generic Type 문제는 코드 플랙스에 MEFGeneric 으로 공개하였습니다.
[.NET/.NET Framework] - MEFGeneric 코드 플랙스에 공개합니다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

때는 바야흐로 2009년 7월이네요. Velocity 를 공부하면서 메모해 놓은 것을 이제서야 발견하여 포스팅을 하고 있습니다. ^^;

현재는 Windows Server AppFabric 이라는 이름으로 공개가 되고 있으며, 코드명은 바로 "Velocity" 라는 이름입니다. 현재 AppFabric Beta 1 까지 출시되었고 이제는 거의 모습을 찾아가고 있는 것 같습니다. 차후에 Velocity 의 현재 제품이름인 AppFabric 을 자세히 살펴보기로 하며, Velocity CTP 3 기준으로 설치와 사용 방법을 간단히 알아보고자 합니다.

   

Why Windows Server AppFabric (Codename "Velocity") ?

Velocity 는 분산 캐싱 프레임워크입니다. 우선 분산 캐싱이 왜 필요한지 이해가 필요합니다. 기존에는 캐싱이라고 함은 in-proc 캐싱을 의미했으며 즉 메모리 상에서 객체를 캐싱(Caching)하거나 풀링(Pooling)하기 위해 시스템의 리소스(Resource) 를 사용했습니다.

하지만 점차 엔터프라이즈 솔루션은 대규모, 대용량화 되어감에 따라 in-proc 캐싱은 시스템 리소스나 성능에 영향을 받게 되었습니다. 기존의 엔터프라이즈 솔루션은 데이터베이스의 대용량 아키텍처에 민감했고, 즉 데이터 중심의 아키텍처링을 할 수 밖에 없었습니다. 데이터의 정합성, 안정성, 성능은 기업에서 돈(Money) 와 직결되는 문제이기 때문이죠.

하지만 이미 데이터와 관련된 기술과 노하우는 이미 포화 상태이고, 엔터프라이즈 전체적인 아키텍처를 보았을때 단지 병목은 데이터에서만 존재하는 것이 아니었다는 것입니다. Middleware 나 Application Server 의 아키텍처링도 이미 포화 상태이고, 이것을 극복하기 위해서는 바로 캐싱(Caching) 이라는 기술이 필요했습니다.

위에서도 언급하였듯이 in-proc 캐싱은 굉장히 단순한 아키텍처입니다. 서버의 리소스가 받쳐 주느냐 그렇지 않느냐의 문제였고 in-proc 그리고 더 나아가 out-proc 를 이용하여 서버 자원을 최대한 활용하고자 합니다. 하지만 여기에서 또 문제가 발생합니다. 분산 out-proc 캐싱을 하자니 분산된 캐싱 데이터의 정합성을 어떻게 보장하느냐 입니다. 즉, out-proc 로 인해 캐싱은 중앙 집중화가 될 수 밖에 없으며 이것은 서버의 리소스에 의존하는 문제의 원점으로 돌아간다는 것이죠.

   

About Windows Server AppFabric (Codename "Velocity")

이러한 엔터프라이즈 환경의 서비스 확장에 대해서 고질적인 문제였던, 그리고 성능을 극대화 할 수 있는 캐싱이라는 기술을 어떻게 활용하느냐에 관심을 갖게 되었습니다. 현재 이런 문제를 해결할 수 있는 솔루션이 Windows Server AppFabric(Codename "Velocity") 입니다.

데이터의 정합성, 안정성, 성능은 기존의 아키텍처를 버리고 전용 Repository 를 통해 해결할 수 있습니다. 그것은 데이터베이스가 될 수 있고, 그 밖에 다른 Repository 가 될 수 도 있겠죠. 바로 이러한 컨셉은 캐싱을 어떤 분산 시스템간이라도 공유한다는 의미입니다. 이러한 캐싱을 클러스터링한다는 것은 흔히 Caching Dependency 를 해결할 수 있는 아주 좋은 해결 방법이기도 합니다. 어떤 로컬 시스템이건, 어떤 원격 시스템이건 캐싱 정책을 적용받게 되는 것입니다.

   

   

   

Install Windows Server AppFabric (Codename "Velocity")

아래는 필자는 게으름으로 Velocity CTP 3 기준으로 설치하는 방법입니다. (지금이라도 포스팅 하는걸 보면 대견스럽습니다만;;;)

기본적으로 캐싱 데이터는 데이터베이스를 사용합니다. 데이터베이스의 파일이 저장이 될 경로를 입력하거나 Storage 타입을 정하시면 됩니다.

 

   

Posted by 땡초 POWERUMC

댓글을 달아 주세요

.NET Framework 4.0 에 포함이 될 Managed Extensibility Framework(이하 MEF) 는 Generic Type 을 지원하지 않습니다. ( MEF is not supporting Generic Type!!!! )   

상당히 충격입니다. MEF 는 현재 Generic Type 을 지원하지 않습니다. 이것을 가지고 현재 중요한 프로젝트를 진행하기 위해 여러 가지 리뷰를 해 보고 있습니다만, MEF 가 Generic Type 을 지원하지 않는 것은 쉽게 말해 'MEF 는 아직…' 이라는 결론이 나는군요.    


Managed Extensibility Framework Basic

이것을 이해하기 위해서는 MEF 의 기본부터 이해해야 할 필요가 있습니다. 자세한 내용은 아래의 필자의 블로그 링크를 클릭하시면 Managed Extensibility Framework 에 대한 아티클을 볼 수 있습니다.

Managed Extensibility Framework
http://blog.powerumc.kr/tag/Managed%20Extensibility%20Framework

우선 이러한 원인은 MEF 가 Contract Model(계약 모델) 기반이라는 있다는 이유 입니다. 우리가 흔히 사용하는 계약 모델은 쉽게 이야기하면 제공자와 소비자로 구분할 수 있습니다. 제공자와 소비자의 거래가 성립이 되기 위해서는 바로 계약이라는 것이 필요하죠. MEF 로 비유하자만 Import/Export 가 바로 그것이며 그 계약을 성립시켜 주는 것이 MEF Container 와 Composition Batch 로 볼 수 있습니다.

바로 이러한 계약 기반과 Composable Part 라는 개념으로 기존의 컴포넌트의 재사용성을 높일 수 있게 되며, 좀 더 동적이며, 추상화가 가능한 프레임워크 입니다. 더 쉽게 얘기하면, 새로운 C 라는 컴포넌트는 A 와 B 라는 컴포넌트와 계약하여 결합시키거나, 기존 컴포넌트를 변형시키는 등 Composable Application 을 만들기 위해 계약의 명세만 알면 다양한 컴포넌트를 재생산, 변형, 다양성, 재활용 등을 할 수 있습니다.

 

MEF 는 내부적으로 이러한 명확한 계약을 위해 여러 가지 방법으로 계약을 정의할 수 있습니다. 기본적으로 ExportAttribute 을 사용하여 String, CLR Type, ExportMetadata 를 사용하게 되어 있지요. 하지만 MEF 는 모든 계약의 명세는 바로 String 을 사용하는 데에서 문제가 발생하게 됩니다. 그리고 이것이 Dependency Injection(DI) 와 Inversion Of Control(IoC) 와 다른 점입니다. 대부분의 DI 프레임워크는 Object 의 Lifecycle 을 관리하고 객체의 의존성을 낮추기 위해 역제어 하는 것에 초점이 맞추어져 있기 때문에 CLR Type 기반으로 Container 에 등록이 됩니다.

예를 들어 보면, 아래와 같은 것이 MEF 에서는 계약 명세 규격에 어긋난다는 의미입니다. (특정 DI 프레임워크에 종속되지 않는 코드입니다)

var container = new Container();
container.Register<IUMC<>>();

var obj = container.Resolve<IUMC<string>>();
obj.SayHello();

   

Why MEF is not supporting Generic Type?

MEF 가 Generic Type 을 지원하지 않는 것에 이미 많은 사람들이 문제를 발견했고, 몇 가지 해결 방법이 있긴 있습니다.

이미 Ayende Rahien 이라는 사람의 블로그에는 MEF 가 Generic Type 을 지원하지 않는 것에 대한 이야기를 합니다. 내용을 보면 처음부터 Microsoft 의 MEF 개발 팀은 Generic Type 을 배제하고 있었던 것 같습니다. 하지만 Ayende Rahien 씨는 이 문제에 대해 반드시 해결해야 한다는 이야기를 MEF 개발 팀과 나누었습니다. 저도 이 문제가 반드시 해결 되리라 생각합니다만… 현재로써는 글쎄 ^^;

여기에서 MEF 개발 팀은 조금 구차한 변명을 합니다. 위에서 얘기한 MEF 의 기본은 계약 기반의 프레임워크라는 것입니다. 이 문제에 대해 추측을 해보면, MEF 가 Generic Type 을 지원한다는 것은 Strongly Contract Based 가 될 수 없기 때문이고, Generic Type 으로 인해 명확한 계약이 이루어질 수 없다는 것입니다. 특히 MEF 는 계약의 명세가 모두 MEF 가 내부적으로 관리하고 있기 때문에, Generic Type 에 의한 객체 의 계약 관리는 엄청난 메모리 사용량을 증가로 이어질 가능성이 충분합니다.

실제로 Microsoft 에서 MEF 개발 팀의 PM 을 맡고 있는 Glenn Block 씨는 이 아티클에서는 MEF v1 에서는 Generic Type 을 지원하지 못할 것이라고 합니다. 만약에 Generic Type 을 지원하게 된다면 차기 버전이 될 듯 합니다.

하지만, 다시 한번 MEF 는 계약 기반의 모델이라는 것을 생각하지 않을 수 없습니다. 만약 계약이 명확하지 않다면 계약 자체가 불명확하다는 의미입니다. C# 2.0 부터 지원하는 Generic Type 의 명확하지 않는 타입이 계약에 존재한다면 이것은 계약 자체가 성립되기 힘들다는 전제 조건을 포함하게 됩니다.

MEF 의 예를 들어 봅시다. 아래와 같은 Generic Type 의 계약이 존재합니다. (현재의 MEF 로는 전혀 불가능한 코드입니다^^;)

public interface IUMC<T>
{

void SayHello<T>();
}

[Export(typeof(IUMC<>))]
public class UMC<T> : IUMC<T>
{
public void SayHello()
{
// TODO Impl...
}
}

CLR(Common Language Runtime) 의 Generic Type 의 특성상 Generic T Parameter 는 굉장히 다형적입니다. UMC<string> 또는 UMC<int> 또는 모든 Class Type 이 T Parameter 에 대입될 수 있습니다. 단순히 어떤 타입도 올 수 있다는 것을 떠나 물건을 팔 사람은 도대체 소비자가 누구와 계약한 것인지 알 수 없고, 실제 상거래와 같은 상황이라면 사기와도 같다는 것이죠. 굳이 예를 들자면, 주민등록번호가 다름에도 불구하고, 주민등록증의 이름이 같은 동명인에게 언제든지 계약을 할 수 있다는 것이죠.

DI(Dependency Injection / IoC) 는 CLR Type 을 기반으로 합니다. 일부 DI 프레임워크는 Tag 와 같은 Contract Data 를 제공하기는 하지만 이것은 Metadata 그 이상의 역활을 하지 않습니다. 즉 Contract(계약) 와는 전혀 무관하다는 이야기 입니다. 객체를 질의(Query) 하기 위함이지 Composable 을 위한 것은 아닙니다.

 

OK! I'm understand. But…!!

처음부터 MEF 는 계약 기반의 Composable/Plugin Model/Contract Based 라는 용어를 자주 만나게 됩니다. 그리고 계약 자체라는 의미에서 Generic Type 은 가장 큰 장애 요소임이 확실합니다. 그렇기 때문에 현존하는 모든 DI(Dependency Injection) 프레임워크는 계약(Contract) 라는 용어를 절대 사용하지 않습니다. 목적 자체가 계약과는 전혀 무관하기 때문입니다.

하지만, MEF 의 계약 모델은 내부적으로 String Based Contract 를 사용하고 있고, Generic Type 또한 String 으로 표현이 가능하기 때문에, 문자열의 Parsing 만으로 어느 정도의 Generic Type 을 지원할 수 있을 거라고 생각했습니다.

필자는 처음 MEF 를 본 순간 "이것을 물건이다!" 라는 걸 느꼈습니다만, 아마도 MEF 개발 팀은 두 가지의 고민을 했을 거라고 생각합니다. Silverlight 를 지원할지, Generic Type 을 지원할지에 대한 범용성에 대해서 말입니다. 하지만, Generic 에 대해 많은 피드백을 받음에도 불구하고 MEF v1 에 지원하지 않을 듯한 대답은 사실 "구차한 변명" 으로 밖에 들리지 않는답니다. 결국, 현재 MEF 는 Silverlight 를 지원하는 등 .NET Framework 의 범용성에 치중하였고, 결국 Generic Type 은 현재 시점에서 릴리즈 시점까지 구현이 불가능할 거라고 예상합니다.

아쉽긴 하지만, 현재 MEF 가 불가능한 Generic Type 에 대한 영역은 몇 가지 Open Source 에서 제공을 하고 있습니다. 단지 실제 사용성에 대한 의구심과 필자의 견해로는 안쓰는게 나을 것 같다는 판단입니다.

다음에 당장 지원하지 않는 Generic Type 을 어떻게 사용할지 알아보고 함께 돌파구를 찾아보도록 하겠습니다.


MEF 에서 Generic Type 문제는 코드 플랙스에 MEFGeneric 으로 공개하였습니다.
[.NET/.NET Framework] - MEFGeneric 코드 플랙스에 공개합니다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

안녕하세요. VSTS 2010 공식 팀에서 Twitter 를 시작했습니다. Twitter 를 통해 차마 시간이 없어 정리하지 못한 정보나 알아두면 좋은 팁과 정보 등을 단문 메시지로 여러분들에게 전달해 드릴 예정입니다.

차세대 플랫폼인 Visual Studio / Team System 2010 등 관심 있는 분들의 많은 Following 을 부탁 드립니다.

http://twitter.com/vsts2010

   

 

   

Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 하나둘넷 2009.10.06 23:42 Address Modify/Delete Reply

    트위터에 좋은 내용들이 짧게 올라오네용.. 잘 보고 가요~^^

    • 땡초 2009.10.07 09:17 Address Modify/Delete

      저는 트위터를 twhirl 이라는 클라이언트 어플케이션으로 사용하고 있답니다.
      제가 써 본 것중에는 제일 편하네요.
      http://www.twhirl.org/download

윈도우 표준 사운드 파일 형식으로 WAVE 가 있다. .wav 형식의 확장자를 사용하며, 시스템이 사용하는 WAVE 파일은 C:\Windows\Media 폴더에 보면 있다.
 
이러한 .wav 파일을 재생하는 방법이 .NET Framework 2.0 에 추가되었다. SoundPlayer 라는 클래스를 사용하고, 이 클래스를 이용하여 사운드를 재생하는 방법을 소개한다.
 
 
동기적으로 .wav 파일 재생하기
 
동기적으로 .wav 파일을 재생하려면, SoundPlayer 의 PlaySync 메서드를 호출하면 된다. 생성자에 .wav 파일의 경로를 적어주는 것으로 객체를 생성하면 된다.
 
다음은 PlsySync 를 이용한 .wav 파일 재생 샘플이다.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Media;
 
namespace ConsoleTest2
{
         class Program
         {
                  static void Main(string[] args)
                  {
                           SoundPlayer player = new SoundPlayer(@"C:\Windows\Media\tada.wav");
                           player.PlaySync();
 
                           Console.WriteLine("재생 완료");
                  }
         }
}
 
[예제1] 동기적으로 .wav 파일을 재생한다
 
샘플을 실행하면 “타다~~” 라는 음향이 들리고, 재생이 완료되면 “재생 완료” 라는 텍스트가 출력된다.
즉, PlaySync 메서드의 호출로 재생이 완료되기 전 까지는 다음 코드가 실행되지 않는다.
 
 
비동기로 .wav 파일 재생하기
 
비동기적으로 사운드를 재생하기 이해서 LoadAsync 와 Play 메서드를 제공한다. .wav 파일을 완전히 로드하기 위해 약간의 딜레이가 필요하고, 사운드 파일이 완전히 로드 되었다는 이벤트로 LoadCompleted 이벤트를 제공한다.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Media;
 
namespace ConsoleTest2
{
         class Program
         {
                  static SoundPlayer player = new SoundPlayer(@"C:\Windows\Media\tada.wav");
 
                  static void Main(string[] args)
                  {
                           player.LoadCompleted += new System.ComponentModel.AsyncCompletedEventHandler(player_LoadCompleted);
                           player.LoadAsync();
 
                           Console.WriteLine(".WAV 파일이 로드가 되고 있습니다.");
                           System.Threading.Thread.Sleep(1000);
                           Console.WriteLine("소리를 재생합니다.");
                  }
 
                  static void player_LoadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
                  {
                           if( player.IsLoadCompleted )
                                   player.Play();
                  }
         }
}
 
[예제2] 비동기적으로 .wav 파일을 재생한다.
 
첫번쩨 [예제1] 과는 약간 다른 결과이다. [예제1] 에서는 재생이 완료되기전까지 다음줄의 코드가 실행되지 않았다.
 
하지만, [예제2]의 비동기 재생에서는 LoadAsync 메서드로 비동기적으로 사운드 파일을 읽어, LoadCompleted 이벤트가 발생하는 시점에 사운드가 재생된다.
Console Project 이므로, 어플케이션 쓰레드가 종료되는 것을 방지하기 위해
 
Thread.Sleep(1000);
 
을 하였다.
 
.NET Framework 2.0 이 제공하는 SoundPlayer 의 몇가지 메서드나 이벤트를 살펴보면, 안타깝게도 재생이 완료되는 시점의 이벤트가 제공되지 않는 것이 아쉽다.
 
.NET Framework 3.5 에서의 SoundPlayer 클래스를 살펴봤지만, 앞으로도 크게 변화는 없을 것 같다.

# 위 글은 http://www.atmarkit.co.jp/ 를 토대로 새로이 각색하였습니다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

오늘은 비동기 ADO.NET 을 알아볼까 합니다.
아티클을 이해하기 위해서는 비동기 호출에 대한 지식이 약간 필요합니다.
 
우리는 다음과 같은 시간이 오래 걸리는 쿼리를 날릴것입니다.
WAITFOR DELAY '00:00:03'; SELECT * FROM Titles
WAITFOR DELAY '00:00:05'; SELECT * FROM Titles
WAITFOR DELAY '00:00:10'; SELECT * FROM Titles
 
위의 WAITFOR DELAY 는 명령문이 암시하듯 이유없는 대기를 하라는 명령입니다.
3초후에 SELECT, 5초후에 SELECT, 10초후에 SELECT 쿼리를 날려 시간이 오래걸리는 작업을 대체하기 위해서 이지요~
 
다음의 쿼리를 쿼리분석기를 통해 실행시켜 보겠습니다.
 
예상대로 18초가 걸리는군요~
하나하나의 쿼리가 종료해야만이 다음 쿼리를 실행할 수 있죠~
이런방식을 동기 방식이라고 합니다.
C# lock 과 같은 키워드가 여러 개의 쓰레드의 요청이 있더라도, 마치 일렬로 줄을 세워 한번에 하나씩 처리하는 것과 비슷하다고 생각하시면 됩니다.
 
그렇다면, 위의 쿼리를 비동기 방식으로 호출하였을 때 어떤 결과가 나올까요?
그럼, 그 의문을 하나하나씩 풀어보기로 합니다.
 
우선 전체 소스를 보면서 비밀을 하나하나씩 파헤쳐 보겠습니다.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
 
namespace ConsoleTest
{
         class Program
         {
                  // 비동기로연결하기위해Asynchronous Processing 를true 로설정해놓은커넥션Connection String
                  public static string GetConnection
                  {
                           get
                           {
                                   return "Asynchronous Processing=true;server=localhost;database=pubs;uid=sa;password=xxxx";
                           }
                  }
 
                  static void Main(string[] args)
                  {
                           SqlConnection cn1 = new SqlConnection( GetConnection );
                           SqlConnection cn2 = new SqlConnection( GetConnection );
                           SqlConnection cn3 = new SqlConnection( GetConnection );
 
                           SqlCommand cmd1            = new SqlCommand("WAITFOR DELAY '00:00:03'; SELECT * FROM Titles", cn1);
                           SqlCommand cmd2            = new SqlCommand("WAITFOR DELAY '00:00:05'; SELECT * FROM Titles", cn2);
                           SqlCommand cmd3            = new SqlCommand("WAITFOR DELAY '00:00:10'; SELECT * FROM Titles", cn3);
 
                           cn1.Open();
                           cn2.Open();
                           cn3.Open();
 
                           // DataAccess 시작시간을기록한다.
                           DateTime startDate = DateTime.Now;
 
                           // 비동기작업을시작한다.
                           IAsyncResult result1       = cmd1.BeginExecuteReader();
                           IAsyncResult result2       = cmd2.BeginExecuteReader();
                           IAsyncResult result3       = cmd3.BeginExecuteReader();
 
                           WaitHandle[] waitHandle    = new WaitHandle[3];
                           string[] resultStr                 = new string[3];
 
                           waitHandle[0]                      = result1.AsyncWaitHandle;
                           waitHandle[1]                      = result2.AsyncWaitHandle;
                           waitHandle[2]                      = result3.AsyncWaitHandle;
 
                           for (int i = 0; i < waitHandle.Length; i++)
                           {
                               // 배열의핸들이모두신호를받을때까지기다립니다.
                               int endIndex           = WaitHandle.WaitAny( waitHandle, 20000, false );
 
                               switch (endIndex)
                               {
                                   case 0:
                                       cmd1.EndExecuteReader(result1);
                                       resultStr[0]   = DateTime.Now.ToString();
                                       break;
                                   case 1:
                                       cmd2.EndExecuteReader(result2);
                                       resultStr[1]   = DateTime.Now.ToString();
                                       break;
                                   case 2:
                                       cmd3.EndExecuteReader(result3);
                                       resultStr[2]   = DateTime.Now.ToString();
                                       break;
                               }
                           }
 
 
                           cn1.Close();
                           cn2.Close();
                           cn3.Close();
 
                           // DataAccess 작업완료시간을기록한다.
                           DateTime endDate = DateTime.Now;
 
                           for( int i=0; i<resultStr.Length;i++)
                                   Console.WriteLine( " Result {0} : {1}", i, resultStr[i] );
 
 
                           // 두시간의시간차를구하여출력한다.
                           TimeSpan timeSpan = endDate - startDate;
 
                           Console.WriteLine("총작업시간은: {0}", timeSpan.ToString() );
                  }
         }
}
 
 
우선 가장 눈에 띄게 차이 나는 것이 ConnectionString 입니다.
Asynchronous Processing=true;server=localhost;database=pubs;uid=sa;password=xxxx
Asynchronous Processing=true는 비동기로 DataBase 에 연결하기 위해 반드시 추가해 주어야 합니다.
 
또한 비동기 데이터베이스 작업을 하기 위해 SqlCommand 클래스는 세가지의 비동기 메서드가 존재합니다.
대부분의 비동기 메서드들이 Begin 으로 시작하는 네이밍을 갖는 것과 마찬가지로, BeginExecuteNonQuery(), BeginExecuteReader(), BeginExecuteXmlReader()가 바로 그것입니다.
이 메서드는 각각 EndExecuteNonQuery(), EndExecuteReader(), EndExecuteXmlReader() 가 호출됨으로써 비로서 비동기 작업의 콜백을 받을 수 있습니다.
위의 경우는 구현되지 않았지만, DataReader 의 경우 콜백이 완료된 후가 되어야지만 비로서 Read 작업을 수행할 수 있는것입니다. 물론 다른 비동기 메서드로 마찬가지입니다.
 
이 구문은 비동기 ADO.NET 작업의 가장 중요한 부분입니다.
waitHandle[0]                      = result1.AsyncWaitHandle;
waitHandle[1]                      = result2.AsyncWaitHandle;
waitHandle[2]                      = result3.AsyncWaitHandle;
WaitHandle.WaitAny( waitHandle, 20000, false );
 
우리는 waitHandler 배열에 비동기 작업이 끝나는데 사용되는 AsyncWaitHandle 를 넣었습니다.
AsyncWaitHandler 는 비동기 작업이 끝나기를 기다리는 핸들러 입니다.
물론 WaitAny 메서드의 3가지 Overload 는 모두 첫번째 인자를 배열로만 받습니다.
WaitWay 메서드는 우리가 넣은 배열의 요소중에 먼저 끝낸 배열의 Index 를 return 합니다.
waitHandler[2] 의 작업이 제일 먼저 끝나게 되면 배열의 인덱스인 2 를 리턴하게 됩니다.
만약 짧은 시간동안 어느 작업도 끝나지 않게 되면, WaitAny 는 두번째 인자에 적은 시간(밀리초)만큼 무한 대기 하게 됩니다.
 
배열에 개수만큼 루프를 돌면서, 비동기 작업이 끝나는 순서대로 SqlCommand 의 작업을 하게 됩니다.
때문에 결과 화면의 DateTime.Now 의 시간은 각각 달라질 수 있는 것입니다.
 
그럼 실행화면을 보겠습니다.
 
보는대로 총 소요시간은 10초가 되었습니다.
위의 동기작업의 쿼리 수행 결과보다 훨씬~ 빨라졌지요?
각가 쿼리 수행이 완료된 시간도 한번 눈여겨 볼만 하네요~
 
긴 시간이 소요되는 작업이나 여러 번 반복적으로 실행되야 하는 작업등에 적용해 보면 상당히 만족할만할 결과가 아닐 수 없네요~
물론 ASP.NET 의 웹 환경에서도 적용이 가능합니다.
 
그럼 이것으로 오늘 강좌 마칩니다. 뱌뱌 ^^//
Posted by 땡초 POWERUMC

댓글을 달아 주세요