Strongly Typed Metadata
 
지난 포스트의 [MEF] 7. Exports and Metadata를 통해 Export 의 Contract 에 Metadata 를 제공하는 방법을 알아보았습니다.
 
MetadataAttribute 을 선언하여 Export 의 Metadata 를 제공하는 방법입니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
Export 에 Metadata 를 제공해 주어서 무척 고맙지만, 위의 방법처럼 MetadataAttribute 를 선언하는 방법을 사용하기에 그다지 내키지 않는 구석이 있습니다.
 
아무래도 아래와 같은 이유 때문이겠죠?
 
l 메타데이터의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
위의 이유 때문에 Metadata 를 사용하기 위해 강력한 타입을 원하게 될 것입니다. 그리고 강력한 타입의 Metadata 를 사용하길 권장 드립니다.
 
 
Declaring Strongly Typed Metadata
 
Attribute 을 상속받아 Export 의 Metadata 를 Strongly Typed 으로 확장시키는 방법입니다.
 
아래의 Stringly Typed Metadata 를 위해 Attribute 클래스를 만드는 소스 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
public enum MessageSenderTransport
{
        Email,
        Phone,
        Sms
}
 
Strongly Typed Metadata 를 위한 Attribute 클래스를 완성하였으면, 이것을 그대로 ExportAttribute 과 함께 추가적으로 선언하면 됩니다.
 
그리고 ExportAttribute 을 선언한 코드에 위의 Attribute 특성을 부여합니다. 아래는 그 소스 코드의 일부입니다.
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Email)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Phone, true)]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(Transport=MessageSenderTransport.Sms, IsSecure=true)]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
그렇다면 아래와 같이 Metadata 를 Strongly Typed 으로 질의(Query) 할 수 있게 됩니다.
 
아래의 소스 코드는 지난 포스트의 소스 전체 소스 코드를 참고하십시오.
 
foreach (var export in program.Sender)
{
        if ((MessageSenderTransport)export.Metadata["Transport"] == MessageSenderTransport.Sms)
        {
               export.GetExportedObject().Say();
 
               if ((bool)export.Metadata["IsSecure"] == true)
               {
                       Console.WriteLine("Security message");
               }
        }
}
 
위의 소스 코드 실행 결과는 원하던 결과대로 다음과 같습니다.
 

[그림1] Strongly Typed 질의 결과
 
 
하지만 아직도 문제는 남아 있는 것 같아 보이네요. Strongly Typed 으로 Export Metadata 를 선언하였지만 여전히 질의(Query) 과정은 똑같은 문제점을 가지고 있습니다.
 
l 메타데이터의 질의(Query) 과정의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
 
More Strongly Typed Metadata Query
 
Metadata 를 질의(Query) 하기 위해 MEF 는 보다 강력하게 Import 하는 방법을 제공해 줍니다. 확장된 MetadataAttribute 에 인터페이스(Interface) 를 구현하도록 하여 Import 시에 인터페이스(Interface) 를 통한 Metadata 를 질의(Query) 하는 방법입니다.
 
우선 Attribute 에 사용되는 프로퍼티를 인터페이스로 선언합니다.
 
public interface IMessageSenderTypeAttribute
{
        bool IsSecure { get; }
        MessageSenderTransport Transport { get; }
}
 
단, 여기에서 반드시 Getter 만 선언하셔야 합니다. Setter 를 선언하시면 MEF 는 Setter 프로퍼티로 인해 유효하지 않는 Attribute 으로 인식하여 예외를 발생하게 됩니다.
 
아래의 소스 코드는 Metadata Attribute 클래스에 위의 인터페이스를 구현한 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute, IMessageSenderTypeAttribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
 
이제 Export 의 MetadataView 로 질의할 수 있도록 MetadataView 의 인터페이스를 알려주도록 해야 합니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
 
아래의 소스 코드는 MetadataView 를 통해 Strongly Typed 으로 Export Metadata 를 질의(Query) 하는 소스 코드 입니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
                                             
static void Main(string[] args)
{
        Program program = new Program();
        program.Run();
 
        foreach (var export in program.Sender)
        {
               if (export.MetadataView.Transport == MessageSenderTransport.Email)
               {
                       export.GetExportedObject().Say();
               }
        }
}
 


'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 10. Querying the CompositionContainer  (0) 2009.05.18
[MEF] 9. Recomposition  (1) 2009.04.19
[MEF] 7. Exports and Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09

[MEF] 7. Exports and Metadata

Managed Extensibility Framework 2009. 4. 16. 01:11 Posted by POWERUMC
Exports and Metadata
 
Export 에 Metadata 를 등록하고 제어하는 방법입니다. 지난 포스트에서 알 수 있듯이 Export 는 구성 요소간에 Contact 를 제공하여 이들을 구성(Composition) 할 수 있는 플러그인 모델(Plugin Model) 을 제공해 줍니다.
 
하지만 Contract 가 제공되어 구성 요소를 확장하고 구성하는 것은 매우 용이하다는 것을 알게 되었으나 Contract 로 인해 파생된 다양한 구성 요소를 어떻게 제어하느냐의 고민을 하게 됩니다. 즉, 다양한 구성 요소 가운데 내가 필요로 하는 구성 요소를 골라낼 수 있도록 Metadata 를 제공하여 구성 요소를 질의(Query)할 수 있도록 하는 것입니다.
 

예를 들어 아래와 같은 Export 구성 요소 중 어떻게 EmailMessageSender 로 Say() 를 호출할 것인가가 문제인 것이죠.

[Export(typeof(IMessageSender))]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
이런 경우, 원초적인 방법으로 리플랙션(Reflection) 을 이용하여 Type 검사를 통해 EmailMessageSender 를 골라내서 사용하면 되지만, 직접적으로 Type 을 검사하기 위해서는 Tightly Coupling 이 발생하여 결국 유연한 플러그인 모델을 구현하기 위해 아무런 도움이 되지 않습니다.
 
이러한 방법을 해소하기 위해 또 다른 우회 방법은 리플랙션을 통해 Modules Name 으로 비교하는 방법이지만, 이것 또한 플러그인 모델에서 구성 요소가 교체 되었을 경우를 생각하면 전혀 대응할 수 없는 방법입니다.
 
MEF 에서는 이런 문제를 해소하기 위해 Contract 에 Metadata 를 제공하며 정적/동적인 방법을 제공해 줍니다.
 
 
Attaching Metadata to an Export
 
Export 에 Metadata 를 제공해 주기 위해서 MEF 에서는 ExportMetadataAttribute 특성을 제공해 줍니다.
 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Field,
                    AllowMultiple = true, Inherited = false)]
public ExportMetadataAttribute(string name, object value)
 
왜 ExportMetadata 클래스는 sealed 로 선언이 되었나요?
 
일반적으로 sealed 로 클래스를 봉인할 경우 리플랙션의 성능이 향상됩니다.
 
 
ExportMetadata 는 키와 값(Key/Value) 을 지정하여 Contract 에 Metadata 를 제공해 줄 수 있습니다. 그리고 하나의 Export 에 여러 개의 Metadata 를 제공할 수 있도록 AllowMultiple 을 지원합니다.
 
Metadata 는 여러 가지의 정보를 포함할 수 있습니다. Export 가 제공하는 기능의 특성을 기술할 수 있으며, 예를 들어, 권한, 로깅, 구성 요소 분류 방법 등이 될 수 있을 것입니다.
 
아래의 소스 코드는 Metadata 를 지정하는 예를 보여줍니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Phone")]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Sms")]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
 
 
Constraining Imports statically
 
MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
MEF Preview 4 에서는 선언적인 방법으로 ImportRequiredMetadataAttribute 를 통해 Metadata 를 질의할 수 있었으나, MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
아마도 추측으로는 ImportRequiredMetadataAttribute 를 선언 시에 여러 개의 구성 요소가 검색될 경우 Exception 이 발생하는데, Exception 을 최소화 하고자 제거가 된 것 같습니다.
 
혹시 Statically 한 방법으로 ImportRequiredMetadataAttribute 에 대응되는 클래스를 아시면 저에게 알려주세요.
 
 
Constraining Imports dynamically
 
이 방법은 Export 의 ExportMetadata 를 런타임 시에 질의(Query) 하는 방법입니다.
 
Import 시 ExportCollection<T> 을 사용하여 Export 를 수동적으로 질의(Query) 하는 방법입니다. 이 방법은 지난 포스트의 Lazy Load 를 이용한 방법으로 단지 Metadata 만 질의(Query) 뿐이고, 객체의 생성에 대한 판단은 필요 시에만 GetExportedobject() 메서드를 이용하여 생성할 수 있습니다.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
 
namespace MetadataSample
{
        class Program
        {
               [Import(typeof(IMessageSender))]
               ExportCollection<IMessageSender> Sender { get; set; }
 
               static void Main(string[] args)
               {
                       Program program = new Program();
                       program.Run();
 
                       foreach (var export in program.Sender)
                       {
                              if ((string)export.Metadata["SenderType"] == "Email")
                                      export.GetExportedObject().Say();
 
                              if (export.Metadata.ContainsKey("Logging") &&
                                      (bool)export.Metadata["Logging"] == true)
                                      Console.WriteLine("Logged success");
                       }
               }
 
               void Run()
               {
                       var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
                       var container = new CompositionContainer(catalog);
                       var batch = new CompositionBatch();
                       batch.AddPart(this);
                       container.Compose(batch);
               }
 
               public interface IMessageSender
               {
                       void Say();
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Email")]
               [ExportMetadata("Logging", true)]
               public class EmailMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import EmailMessageSender");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Phone")]
               public class PhoneMessageSneder : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import PhoneMessageSneder");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Sms")]
               public class SmsMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import SmsMessageSender");
                       }
               }
        }
}
 
 
실행 결과는 예상할 수 있듯이 아래와 같이 런타임 시에 결과를 보여줍니다.


'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 9. Recomposition  (1) 2009.04.19
[MEF] 8. Strongly Typed Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09
[MEF] 4. Import 선언  (0) 2009.04.07

[MEF] 5. Catalog 사용

Managed Extensibility Framework 2009. 4. 9. 21:31 Posted by POWERUMC
Catalog
 
Catalog 는 동적으로 Composable Part 를 찾아 Container 에 등록합니다. Composable Part 는 ExportAttribute 으로 Contract 를 선언할 수 있는데 개별적으로 일일이 Composable Part 를 등록하는 것은 너무나 큰 반복 작업이 될 수 있지만, MEF 의 Catalog 로 쉽게 자동적으로 등록을 할 수 있습니다.
 
Catalog 를 사용하지 않는 Composable Part 의 등록는 매우 고단한 작업입니다. 아래의 예제 소스 코드는 수동으로 Composable Part 를 등록하는 방법입니다.
 
 
예제에서는 단지 하나의 Export 를 등록하였지만 실제로 이러한 Composable Part 는 수십에서 수백개를 넘을 수 도 있습니다. 많은 Composable Part 를 동적이고 또는 자동적으로 등록해 주기 위해서 Catalog 를 사용하면 쉽게 해결할 수 있습니다.
 
 
이 외에도 Composable Part 를 Catalog 에 등록하는 여러 가지 방법이 있습니다. 아래는 MEF 에서 지원하는 Catalog 입니다.
 
 
Assembly Catalog
 
Assembly Catalog 는 닷넷 어셈블리를 Catalog 로 사용할 수 있습니다.
 
public AssemblyCatalog(string codeBase)
public AssemblyCatalog(Assembly assembly)
 
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
 
AssemblyCatalog 의 시그너처(Signature) 에서 보듯이 CodeBase 로 Catalog 를 사용할 수 있지만, 실버라이트에서는 CodeBase 를 지원하지 않으므로 실버라이트에서는 CodeBase 로 AssemblyCatalog 를 사용할 수 없습니다.
 
 
Directory Catalog
 
Directory Catalog 를 사용하면 특정 폴더의 어셈블리를 모두 검색하여 Composable Part 를 사용할 수 있습니다.
 
public DirectoryCatalog(string path)
public DirectoryCatalog(string path, string searchPattern)
 
Directory Catalog 는 내부적으로 System.IO 의 Directory.GetFiles() 메서드를 사용하여 지정한 폴더의 모든 어셈블리를 검색하도록 하여 Composable Part 를 가져오도록 합니다.
 
var catalog = new DirectoryCatalog("Addins");
 
기본적으로 *.DLL 확장자의 어셈블리만 검색하도록 설정되어있고, 어셈블리 파일의 검색 패턴을 지정하여 검색할 수 있습니다.
 
var catalog = new DirectoryCatalog("Addins", "*.exe");
 
아마 MEF 의 예제중에 XFileExplorer 처럼 특정 폴더에 어셈블리를 복사해 넣으면 자동으로 Composable Part 가 로드되는 예제가 있는데, 이것은 DirectoryCatalog 가 자동으로 처리해 주는 것이 아니라 FileSystemWatcher 클래스를 이용하여 직접 구현해야 합니다.
 
FileSystemWatcher 로 파일의 변경이 감지되면 DirectoryCatalog 의 Refresh() 메서드를 이용하여 폴더를 재검색 또는 새로고침을 할 수 있습니다.
 
 
Aggregation Catalog
 
Aggregation Catalog 는 복합적인 Catalog 를 사용할 수 있도록 합니다. 만약 Assembly Catalog 와 Directory Catalog 등을 동시에 사용하여 Composable Part 를 가져오도록 하려면 Aggregation Catalog 를 사용하면 됩니다.
 
public AggregateCatalog(params ComposablePartCatalog[] catalogs)
 
Aggregation Catalog 의 생성자는 params 로 인자를 받을 수 있습니다.
 
var catalog = new AggregateCatalog(   new AssemblyCatalog(Assembly.GetExecutingAssembly()),
                                                                            new DirectoryCatalog("."));
 
 
Type Catalog
 
Type Catalog 는 Composable Part 를 Type 으로 개별적으로 사용하도록 합니다. 가장 단순하고 무식(?)한 방법일 수도 있지만 세세한 제어가 필요할 때 사용 가능한 Catalog 일 것 같습니다.
 
public TypeCatalog(params Type[] types)
public TypeCatalog(IEnumerable<Type> types)
 
var catalog = new TypeCatalog(typeof(EMailMessageSender), typeof(PhoneMessageSender));
 
 
Using catalog with a Container
 
이제 Catalog 를 Composable Container 에서 Composition 하도록 넘겨주는 일만 남았습니다.
 
public CompositionContainer()
public CompositionContainer(params ExportProvider[] providers)
public CompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
 
var container = new CompositionContainer(catalog);
 

'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 7. Exports and Metadata  (0) 2009.04.16
[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 4. Import 선언  (0) 2009.04.07
[MEF] 3. Export 선언  (0) 2009.03.29
[MEF] 2. Parts 와 Contracts 선언  (0) 2009.03.22

[MEF] 3. Export 선언

Managed Extensibility Framework 2009. 3. 29. 21:03 Posted by POWERUMC
Exports 선언
 
MEF 는 Export 를 통해 외부로 구성요소를 노출할 수 있습니다. Export 는 System.ComponentModel.Composition.ExportAttribute 특성을 통해 선언합니다. 이 특성은 클래스 뿐만 아니라 프로퍼티와 메서드에도 선언을 할 수 있습니다.
 
 
구성요소 Export 하기
 
ExportAttribute 특성을 사용하여 아래와 같이 구성요소를 외부로 노출하게 됩니다. ExportAttribute 은 몇 가지의 시그너처(Signature) 를 제공하는데 매개변수를 생략하게 될 경우 MEF 은 클래스의 타입으로 Contract 를 매핑하게 됩니다.
 
[Export]
class MessageSender
{
 
}
 
 
프로퍼티 Export
 
프로퍼티를 Export 하는 방법입니다. 프로퍼티를 Export 할 수 있게 되어 여러 가지 면에서 유리할 수 있습니다.
 
Core CLR 이 제공하는 타입(Type) 뿐만 아니라 외부의 다양한 타입(Type) 을 사용할 수 있습니다. 프로퍼티에 Export 를 선언할 수 있음으로써 Export 를 구조적으로 분리하여 단순화 할 수 있습니다. 그러므로 같은 구성요소 내에서 Export 간의 Related 관계를 가질 수 있습니다.
 
아래의 코드와 같이 Timeout 프로퍼티는 Contract 를 맺게 됩니다.
 
public class Configuration
{
   [Export("Timeout")]
   public int Timeout
   {
       get { return int.Parse(ConfigurationManager.AppSettings["Timeout"]); }
   }
}
 
[Export]
public class UsesTimeout
{
   [Import("Timeout")]
   public int Timeout { get; set; }
}
 
 
메서드 Export
 
구성요소의 메서드를 Export 할 수 있습니다. 메서드의 Export 는 기본적으로 대리자(Delegate) 를 통해 호출하게 됩니다. 메서드를 Export 하게되면 보다 더 세세한 제어를 가능하게 하고, 심플하게 방법으로 Code Generating 이 가능합니다.
 
public class MessageSender
{
   [Export(typeof(Action<string>))]
   public void Say(string message)
   {
       Console.WriteLine(message);
   }
}
 
[Export]
public class MessageProcess
{
   [Import(typeof(Action<string>))]
   public Action<string> MessageSender { get; set; }
 
   public void Send()
   {
       MessageSender("Call send process in MessageProcess");
   }
}
 
그리고 ExportAttribute 은 타입(Type) 대신 문자열을 사용하여 Contract 를 사용할 수 있습니다.
 
public class MessageSender
{
   [Export("MessageSender")]
   public void Say(string message)
   {
       Console.WriteLine(message);
   }
}
 
[Export]
public class MessageProcess
{
   [Import("MessageSender")]
   public Action<string> MessageSender { get; set; }
 
   public void Send()
   {
       MessageSender("Call send process in MessageProcess");
   }
}
 
 
아래의 소스 코드는 이번 예제에서 사용된 전체 소스 코드입니다.
namespace ExportSample
{
   class Program
   {
       [Import]
       MessageProcess MessageProcess { get; set; }
 
       [STAThread]
       static void Main(string[] args)
       {
             Program p = new Program();
             p.Run();
 
             p.MessageProcess.Send();
       }
 
       private void Run()
       {
             var catalog = new AggregateCatalog();
             catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
             var container = new CompositionContainer(catalog);
             var batch = new CompositionBatch();
             batch.AddPart(this);
             container.Compose(batch);
       }
   }
 
   public class MessageSender
   {
       [Export("MessageSender")]
       public void Say(string message)
       {
           Console.WriteLine(message);
       }
   }
 
   [Export]
   public class MessageProcess
   {
       [Import("MessageSender")]
       public Action<string> MessageSender { get; set; }
 
       public void Send()
       {
             MessageSender("Call send process in MessageProcess");
       }
   }
}
 
 
Export 요약
 
이렇게 ExportAttribute 을 사용하여 Contract 를 제공하는 것은 굉장히 중요한 의미를 가지게 됩니다. 플러그인 모델(Plugin Model) 에서 Export 는 구성요소를 외부로 노출하는, 즉 Contract 의 방법을 제공해 주게 됩니다.
 
http://blog.powerumc.kr/upload/Image/NET/NET-Framework/MEF1/capture1.jpg
 
Contract 맺음으로써 개발자는 Contract Base 로 단지 Contract 만 제공받으면 됩니다. 이러한 Contract 는 제한된 상호작용을 극복하여 대부분의 커플링(Coupling)을 해소할 수 있으며, 플로그인 모델(Plugin Model) 에서 보다 쉽게 구성요소를 캡슐화 할 수 있습니다.

'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09
[MEF] 4. Import 선언  (0) 2009.04.07
[MEF] 2. Parts 와 Contracts 선언  (0) 2009.03.22
[MEF] 1. Managed Extensibility Framework 이란?  (2) 2009.03.16
시작하기 전에
 
MEF 는 이미 CodePlex 사이트의 Wiki 에 코드를 중심으로 설명이 잘되어 있습니다. 그렇기 때문에 저도 CodePlex 의 사이트를 참고하여 나름대로 각색하여 작성을 하고자 합니다. 레퍼런스가 이미 CodePlex 에 충분하지만, 저는 나만의 시각에서 바라보고 느낀 바를, 그리고 소스 코드를 만들어 가고자 합니다^^ (사실 Wiki 의 설명은 미약할 다름입니다^^;)
 
CodePlex 의 Wiki 를 먼저 보실 분은 최신 버전이 적용이 되지 않은 예제도 있으니 이런 부분은 조심해서 리뷰하시기 바랍니다.
 
 
어플리케이션에 MEF 호스팅하기
 

[그림1] Composition Container
 
Composable Part
 
MEF 에 어플리케이션을 호스팅하기 위해서는 몇 가지의 반복적인 절차를 거치면 됩니다. 먼저 MEF 를 호스팅할 수 있는 컨테이너(Container) 를 만들어야 합니다. 컨테이너는 MEF 에서 상위 그룹에 존재하며 MEF 의 파트(Part) 를 관리하며 핵심 역할을 담당합니다.
 
MEF 의 컨테이너는 배치(Batch) 를 구조적으로 구성하고 캡슐화 합니다. 배치(Batch) 의 각 구성 단위를 파트(Part) 라고 부릅니다. 각 파트(Part) 는 닷넷 어셈블리에서 타입(Type) 이 될 수 있으며, 타입(Type) 을 배치(Batch) 로 등록할 수 있습니다.
 
파트(Part) 는 메인 호스트(Main Host) 가 반드시 포함되어야 하며, 아래의 소스 코드에서 보는 것처럼 this 키워드가 바로 메인 호스트(Main Host) 가 됩니다.
 
Contract
 
이러한 Composable Part 는 다른 컴포넌트와 의존 관계를 갖지 않습니다. 모든 외부 기능(Export) 는 계약(Contract) 를 맺게되며 이것이 필요할 경우에는 Import 할 수 있습니다.
 
MEF 의 컨테이너는 Contract 정보의 타입 정보나 메타 데이터를 통해 Export 와 Import 를 매칭합니다.
 
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
 
class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }
 
    public void Run()
    {
        var container = new CompositionContainer();
        var batch = new CompositionBatch();
        batch.AddPart(this);
        container.Compose(batch);
    }
}

 
이러한 파트(Part) 를 제어하는 작업은 어플리케이션 차원에서 유일해야 하므로 스레드(Thread) 작업에 안전하도록 lock 으로 블로킹(Blocking) 되어있습니다.
 
이 작업은 메인 호스트(Main Host) 를 등록하는 과정에 불과하며 실행 시에 아무런 결과가 없습니다.
 
 
외부로 기능 노출하기 (Export)
 
이제 어떤 플러그인(Plugin) 을 외부로 노출해야 합니다. 외부로 노출하는 이유는 내/외부에서 노출된 확장 기능을 가져다 쓰기 위해서입니다. 확장 기능을 노출하기 위해서는 단지 Export Attribute 을 정의해 주면 됩니다.
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
 
namespace MEFLab_Hosting_MEF
{
    class Program
    {
        static void Main(string[] args)
        {
               Program p = new Program();
               p.Run();
        }
 
        public void Run()
        {
               var container = new CompositionContainer();
               var batch = new CompositionBatch();
               batch.AddPart(this);
               container.Compose(batch);
        }
    }
 
    public interface IHelloWorld
    {
        void Say();
    }
 
    [Export(typeof(IHelloWorld))]
    public class HellowWorld : IHelloWorld
    {
        public void Say()
        {
               Console.WriteLine("Hello MEF!");
        }
    }

 
인터페이스(Interface) 를 구현한 HelloWorld 클래스는 Export Attribute 를 정의합니다. Export 의 파라메터는 메타 데이터(Meta Data)로 사용되기 때문에 타입(Type) 을 정의해 주어야 합니다.
 
혹자는 왜 인터페이스를 정의해야 하느냐고 궁금해 하기도 합니다. 정답을 드리자면, 반드시 인터페이스를 정의하지 않아도 됩니다. 하지만 특히 MEF 에서 인터페이스는 다형적이고 표준적인 메타 데이터를 제공하기 위해 인터페이스를 선언하여 사용하는 것이 좋을 것 같네요^^
 
 
외부 기능 가져오기 (Import)
 
Export Attribute 으로 외부로 노출된 확장 기능은 Import Attribute 으로 가져올 수 있습니다. 단, 배치(Batch) 의 파트(Part) 로 등록이 되어 있어야 합니다. 내부적으로 Import 의 타입을 생략할 경우 Export 의 타입의 메타 데이터를 통해 Import 의 개체를 쿼리(Query) 하여 생성하게 됩니다.
 

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
    }
 
    [Import]
    IHelloWorld HelloWorldCompoent { get; set; }
 
    public void Run()
    {
        var container = new CompositionContainer();
        var batch = new CompositionBatch();
        batch.AddPart(new HellowWorld());
        batch.AddPart(this);
        container.Compose(batch);
 
        HelloWorldCompoent.Say();
    }
}
 
public interface IHelloWorld
{
    void Say();
}
 
[Export(typeof(IHelloWorld))]
public class HellowWorld : IHelloWorld
{
    public void Say()
    {
        Console.WriteLine("Hello MEF!");
    }
}

 
이 예제에서 알 수 있듯이 코드에서 모듈(Module) 이나 타입(Type) 의 관계를 크게 신경 쓰지 않았습니다. Export 와 Import 를 통해서 의존(Dependency) 는 어느 정도 해소된 것처럼 보입니다. (인터페이스만 잘 활용하면 MEF 없이도 충분히 가능합니다)
 
이것이 끝이라면 정말 재미가 없겠죠. 차차 나오게 될 내용이지만, 이러한 Export 와 Import 를 활용하고 Lazy Load, 그리고 메타 데이터를 이용한 쿼리(Query) 를 활용하게 되면 복잡한 기능을 획기적으로 단순화 할 수 있습니다.
 
 

'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09
[MEF] 4. Import 선언  (0) 2009.04.07
[MEF] 3. Export 선언  (0) 2009.03.29
[MEF] 1. Managed Extensibility Framework 이란?  (2) 2009.03.16
MEF (Managed Extensibility Framework) 란?
 
Menaged Extension Framewkr(이하 MEF) 란? 가장 쉽게 얘기하자면, 어플리케이션과 컴포넌트의 재사용성을 높일 수 있는 프레임워크입니다. 기존의 어플리케이션은 하나의 목적을 하나의 어플리케이션으로 구현한 정적인(Statically) 어플리케이션이라면, MEF 는 보다 동적인(Dynamically) 어플리케이션을 구축할 수 있는 새로운 라이브러리를 제공합니다.
 
기존의 정적인 어플리케이션은 새로운 요구사항으로 기능을 확장할 필요가 있을 경우 새로운 빌드 버전을 필요로 합니다. 그리고 새로운 빌드 버전은 기존 어플리케이션과 확장된 기능간에 종속적인 관계를 탈피할 수 없었습니다. 이미 프로그램 언어를 사용하여 코드를 작성하는 분들이라면 반복적이거나 확장에 대해 많은 고민을 한번쯤 해보았을 것입니다.
 

[그림1] MEF 의 컨셉 이미지
 
MEF 는 플랫폼의 저레벨(Low Level) 에서 공통된 추상적인 기능을 제공하고 여러 곳에 흩어진 이중 작업을 감소시킬 수 있습니다.
 
몇 달 전 마이크로소프트에서는 Application Framework Core 팀이라 부르는 팀이 만들어졌다고 합니다. 이 팀은 ASP.NET, Windows Forms, WPF, Silverlight 의 여러 플랫폼에서 동작하기 위한 BCL(Base Class Library) 과 같은 역할을 한다고 합니다.
 
 
MEF 는 플러그인 모델(Plugin Model)
 
MEF 는 플러그인 모델(Plugin Model) 이라고도 부릅니다. 플러그인 모델을 잘 모르신다면, 전기 콘센트의 플러그를 연상하시면 됩니다. 전기 콘센트의 플러그는 그 수가 제한되어 있지만 어댑터를 추가로 구매하여 꽃으면 몇 개의 콘센트를 꽃을 수 있습니다. 그리고 TV 나 컴퓨터를 사용하고 싶다면 콘센트에 꽃기만 하면 TV 와 컴퓨터를 동작시킬 수 있습니다.
 

[그림2] MEF 는 플러그인 모델(Plugin Model) 이다. (출처는 여기)
 
만약 더 좋은 TV 나 컴퓨터가 생겼다면 플러그를 빼고 더 좋은 TV 나 컴퓨터의 플러그를 콘센트에 꽂기만 하면 됩니다.
 
이처럼 MEF 는 어플리케이션의 확장을 위한 보다 유연한 아키텍처를 제공하며, 약간의 개념만 이해한다면 사용하기도 쉽습니다.
 
 
어디서 많이 본 듯한 아키텍처?
 
맞습니다. 저도 MEF 를 처음 보는 순간 느꼈습니다. MEF 의 외관은 마치 DI(Dependency Injection) 과 IoC(Inversion of Control) 의 그것과 매우 유사해 보였습니다.
 
용어 설명
 
DI(Dependency Injection) / 의존성 주입
객체간의 의존성과 결합도를 낮추기 위한 기술
 
IoC(Inversion of Control) / 역제어
인스턴스의 관리를 컨테이너에서 인스턴스의 생성과 소멸의 생명주기를 관리
 
하지만 MEF 는 DI 나 IoC 와 전혀 다릅니다. 위에서 얘기한대로 MEF 는 플러그인 모델입니다. 어플리케이션의 확장을 위한 프레임워크이며, DI 와 IoC 는 잊어버리십시오! 아래의 단원에서 DI 와 IoC 를 잊을 수 있는 분명한 근거를 설명하도록 하겠습니다.
 
 
플러그인 모델(Plugin Model) 로 뭘 할 수 있나요?
 
MEF 의 플러그인 모델(Plugin Model) 은 보다 나은 확장성을 제공해 줍니다. 마치 Visual Studio 의 Addin 을 연상하시면 됩니다. Visual Studio Addin 은 Visual Studio 의 기능을 쉽고 강력하게 활용할 수 있도록 도와주며 전혀 새로운 기능을 추가할 수 있는 플러그인(Plugin) 입니다. 그럼 MEF 의 플러그인 모델(Plugin Model) 은 어떠한 장점이 있을까요?
 
MEF 의 플러그인 모델은 아래와 같은 장점을 제공합니다.
 
l 확장 기능에 대한 인스턴스를 생성하는 방법(SingleCall, Singleton)을 제공하며, 인스턴스의 회수할 수 있어야 합니다.
l 상호작용을 할 수 있습니다. 확장 기능간에 메시지의 전달이 쉽고 용이해야 합니다. Global Service 처럼 상호간의 인터페이스가 제공되어야 하고 이러한 메시지에 응답할 수 있어야 합니다.
l 확장 기능은 Lazy Load 할 수 있어야 합니다. 확장 기능은 원하는 시기에 로드해야 합니다. 마치 VSIP(Visual Studio Integration Package) 의 VSCT 와 같이 인터페이스나 이벤트는 노출이 되어 있지만 실제로 확장 기능이 로드된 것은 아닙니다.
l 확장이 용이해야 합니다. 예를 들어, 확장 기능이 특정 폴더(Addin 폴더라고 가정)로 복사된다면 이를 감지하여 인터페이스를 제공하거나 확장 기능이 활성화 되어야 합니다.
l 교체가 쉬워야 합니다. 새로운 기능이 추가된 확장 기능은 교체가 쉬워야 하며, 필요 없어진 확장 기능을 언로드(Unload) 할 수 있어야 합니다.
 
MEF 는 여러분의 정적인 어플리케이션에 동적인 생명력을 불어넣어 줄 수 있으며, 그 근간은 바로 플러그인 모델(Plugin Model) 입니다.
 
이러한 플러그인 모델은 근본적인 상호간의 의존(Dependency) 관계를 보다 자연스럽게 해결할 수 있으며, 유연하고 매끄럽게 다룰 수 있을 것으로 기대합니다.
 
MEF 설치하기
 
MEF 는 .NET Framework 4.0 에 포함될 예정입니다. 하지만 아쉽게도 .NET Framework 4.0 CTP 버전에는 포함되어 있지 않기 때문에 Visual Studio Team System 2010 의 VPC 이미지에서도 테스트할 수 있는 환경이 갖추어지지 않았습니다.
 
현재 2009-03-16 기준으로 MEF 는 Preview 4 버전이며 CodePlex 에서 소스코드와 바이너리를 다운로드 받을 수 있습니다.
 
MEF (Managed Extensibility Framework) 다운로드
 
 
 
시작이 반이다! 친절한 HelloWorld 따라하기
 
자… 이제 MEF 도 설치했고 언제나 그렇듯 HelloWorld 를 콘솔에 출력하는 코드를 작성해 보도록 합시다. 소스 코드에 대한 설명은 다음 단원으로 넘기도록 하고, 오늘은 MEF 가 어떻게 동작하는지 눈대중만 익히시면 될 것 같습니다.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
 
namespace ConsoleApplication1
{
       class Program
       {
             static void Main(string[] args)
             {
                    Component com = new Component();
            
                    var catalog = new AggregateCatalog();
                    catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
                    var container = new CompositionContainer(catalog);
                    var batch = new CompositionBatch();
                    batch.AddPart(com);
                    container.Compose(batch);
 
                    com.HelloWorldComponent.Say();
             }
       }
 
       public class Component
       {
             [Import]
             public IHelloWorld HelloWorldComponent { get; set; }
       }
 
       public interface IHelloWorld
       {
             void Say();
       }
 
       [Export(typeof(IHelloWorld))]
       public class HelloWorld : IHelloWorld
       {
             public void Say()
             {
                    Console.WriteLine("Hello MEF");
             }
       }
}
 
 
출력 결과는
 

[그림3] 소스 코드 실행 결과
 
혹자는 HelloWorld 를 찍는데 왜 이렇게 많은 코드가 필요한가에 불만이 있을지도 모릅니다. 맞습니다. 단순히 결과가 HelloWorld 가 출력되기만 바란다면 이렇게 긴(?) 코드는 필요가 없을지도 모릅니다. 하지만 위의 소스 코드는 보다 큰 어플리케이션을 만들기 위한 초석이며 실제로 큰 어플리케이션에서는 훨씬 적은 코드로 복잡한 요구사항을 구현할 수 있을지도 모르는 일입니다^^
 
 
MEF 는 아직은 Preview
 
언제나 그렇듯이 새로운 프레임워크는 새로운 패러다임을 가져옵니다. 그리고 보다 나은 아키텍처와 개발 방법을 제시합니다. 물론 MEF 도 더 깊이 살펴볼수록 그 놀라운 기능에 감탄을 하게 될 것입니다.
 
하지만 아직 MEF 는 Preview 입니다. MEF 의 플러그인 모델은 충분히 활용 가치가 있지만 아직은 실무에서 즉시 도입하기엔 많은 고민이 따를 것 같습니다. 그리고 이런 애로 사항이 아마로 정식 버전에서는 해결되리라 생각합니다. 시간이 되면 이 부분도 한번 다루어 보도록 하겠습니다.

'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 6. Lazy Exports  (0) 2009.04.13
[MEF] 5. Catalog 사용  (0) 2009.04.09
[MEF] 4. Import 선언  (0) 2009.04.07
[MEF] 3. Export 선언  (0) 2009.03.29
[MEF] 2. Parts 와 Contracts 선언  (0) 2009.03.22
Task Parallel Library
 
Parallel Extension 은 PLINQ 와 더불어 확장 가능한 Task Parallel Library 를 제공합니다. Task Parallel Library 는 PLINQ 를 이용하지 않고 개별적이고 수동적인 병렬 처리 작업을 위해 사용할 수 있습니다.
 
Task Parallel Library 는 크게 세 가지 방법으로 병렬 처리를 위한 Library 를 제공합니다.
 
Loops
 
 
 

 

[그림1] Parallel.For 를 이용한 병렬 처리
 
 


[그림2] Parallel.Foreach 를 이용한 병렬 처리
 
Task Parallel Extension 으로 병렬 처리를 쉽게 처리할 수 있으며, 병렬 처리로 인자값을 넘기거나 하는 작업을 쉽게 할 수 있습니다.
 
Statements
 

 


[그림3] Parallel.Invoke 를 이용한 병렬 처리
 
 
Task
 
특히 Parallel Extension Library 에서 Task 는 수동적으로 병렬 처리를 하기 위해 다양한 기능을 지원합니다. 정교하게 스레드(Thread) 를 처리했던 것에 비하면 심플하고도 직관적으로 병렬 작업을 처리할 수 있습니다.
 
Task 는 보다 정교하게 병렬 처리 작업을 할 수 있습니다.
l 대기
l 취소
l 연장
l 상하(부모/자식) 간의 관계
l 디버그 지원
 
아래는 ThreadPool.QueueUserWorkItem 처럼 바로 작업을 시작하도록 합니다.
 

Task.StartNew(…);

 
아래는 Task 에 대해 대기 및 취소 작업을 진행하도록 합니다.
 

Task t1 = Task.StartNew(…);
t1.Wait();
t1.Cancel();
Task t2 = t1.ContinueWith(…);

 
아래는 작업에 대해 지속적인 병렬 처리를 가능하도록 합니다.
 

var p = Task.StartNew(() => {
    var c = Task.StartNew(…);
}

 
아래는 특정 작업의 결과를 받아 올 수 있습니다.
 

var p =
 Future.StartNew(() => C());
int result = p.Value;
 

 
 
Coordination Data Structures
 
병렬 처리 작업은 PLINQ 와 TPL(Task Parallel Library) 를 지원하기 위해 기존의 데이터 컬렉션 등이 등장하였습니다. 내부적으로 동기화를 지원하지 않았던 문제들을 지원하게 되었고, 특히 오늘날 멀티 코어(Multi Core) 프로세스를 위해 많은 동기적인 문제를 고민해야 했습니다. .NET Framework 4.0 은 이러한 공통적인 문제들을 해결하기 할 수 있습니다.
 
l Thread-safe collections
       ConcurrentStack<T>
       ConcurrentQueue<T>
       ConcurrentDictionary<TKey,TValue>
      
l Work exchange
       BlockingCollection<T>
       IProducerConsumerCollection<T>
l Phased Operation
       CountdownEvent
       Barrier
l Locks
       ManualResetEventSlim
       SemaphoreSlim
       SpinLock
       SpinWait
l Initialization
       LazyInit<T>
       WriteOnce<T>

지난 포스트에서 Parallel Extension 과 LINQ 를 이용한 PLINQ 에 대해서 살펴보았습니다. 지난번에 얘기했듯이 Manual Parallelism 는 Parallel Extension 의 성능을 절대 따라올 수 없다고 하였습니다. 왜냐하면, Parallel Extension 은 Manual Parallelism 의 병렬 처리 방식보다 더 복잡하고 정밀한 병렬 처리 알고리즘으로 처리하기 때문입니다.
 
Parallel Extension 이란?
 
Parallel Extension 은 전혀 새로운 것이 아닙니다. C# 3.0 의 LINQ 는 LINQ 쿼리식을 제공하기 위해 새로운 컴파일러(Compiler) 가 필요했습니다. 정확히 말하자면 C# 의 Language Service 의 버전이 업그레이드 되었고, LINQ 쿼리식을 편하게 쓸 수 있도록 Visual Studio 2008 을 사용해야 했습니다. 다시 말하자면, LINQ 쿼리식이 아닌 확장 메서드(Extension Methods) 만으로 쿼리가 가능했다는 것이 이것을 증명해 줍니다. 확장 메서드(Extension Methods) 는 결국 IL 레벨에서는 정적(Static) 인 인스턴스(Instance) 에 불과하니까요.
 
Parallel Extension 은 새로운 컴파일러(Compiler) 가 필요하지 않습니다. .NET 의 기본적인 코어(Core) 인 mscorelib.dll, System.dll, System.Core.dll 만을 사용하여 구현이 되었습니다. 그리고, 기존의 ThreadPool 을 개선하였고, LINQ 와 통합하여 선언적으로 Parallel Extension 을 사용할 수도 있게 되었죠.
 
Task Parallel Library 를 통해 데이터 처리와 어떤 작업(Task)에 대해서도 병렬 처리도 가능해 졌습니다. 이제는 데이터의 병렬 처리뿐만 아니라, 작업(Task) 단위로서도 Task Parallel Library 로 병렬 처리가 가능합니다.
 
이제는 병렬 처리가 된다는 것이 중요한 게 아니라, 병렬 처리의 내부적인 예외 핸들링이나 Visual Studio 에서 디버깅(Debugging) 이 가능합니다. 이러한 새로운 매커니즘으로 내부적으로는 새로운 예외 핸들링 모델(Exception Handling Model)이 필요했습니다.
 
또한 .NET Framework 4.0 의 Parallel Extension 은 다양한 언어를 지원합니다. C#, VB.NET, C++, F# 그리고 .NET 컴파일러(Compiler) 로 컴파일(Compile) 되는 언어라면 상관없습니다. RoR/PHP 라도 .NET 컴파일러(Compiler)에 의해 컴파일(Compile) 된다면 Parallel Extension 을 사용하는데 전혀 문제가 없습니다.
 
 
Parallel Extension 아키텍처
 
 
[그림1] Parallel Extension 아키텍처 (클릭하면 확대)
 
Parallel Extension 의 병렬 처리는 .NET 컴파일러(Compiler) 로 컴파일(Compile) 되는 어떤 언어든 차별을 두지 않고, 병렬 처리 기능을 사용할 수 있습니다. PLINQ 로 작성된 쿼리(Query)는 별도의 PLINQ Provider 의 엔진(Engine) 에서 쿼리를 분석하는 과정을 거치게 됩니다. 쿼리 분석(Query Analysis) 에 의해 선언된 LINQ 쿼리식을 분석하여 최적의 알고리즘으로 각각의 작업을 배치하게 됩니다.
 

[그림2] Parallel Extension 작업 분할
 
각각 분배되는 작업은 쓰레드 큐(Thread Queue) 에 쌓이고, 이 큐에 쌓이는 작업(Task) 는 실제 작업자 쓰레드(Worker Thread) 에 할당이 됩니다. 하지만, Parallel Extension 은 단지 쓰레드에 할당하는 것으로 작업이 마치기를 기다리지 않습니다. 만약, 작업을 분배하는 것은 Manual Parallel 과 크게 다르지 않기 때문이죠.
 

[그림3] Parallel Extension 작업 분할
 
Parallel Extension Library 는 병렬 처리의 작업을 지속적으로 최적의 상태를 감시합니다. 예를 들어, A 의 작업이 Task 1, Task 2, Task 3 인데, B 의 작업은 모두 마친 상태라고 할 때, Parallel Extension Library 는 A 의 작업을 놀고 있는 B 에게 또 다시 분배합니다. 이러한 반복적으로 병렬 처리의 작업이 최적화 될 수 있도록 하여, 병렬 처리의 성능을 극한으로 끌어올립니다.
 
LINQ 만 알면 난 병렬 처리 개발자
 
Parallel Extension 은 LINQ 와 통합하기 위한 프로바이더(Provider) 를 제공합니다. 아직 LINQ 잘 모르신다구요? 30분만 투자하시면 LINQ 를 사용하는데 큰 지장이 없습니다. 그리고 그만큼 쉽습니다. LINQ 를 이해하기 위해 제네릭(Generic), 확장 메서드(Extension Methods), 익명 타입(Anonymous Methods) 도 함께 공부하시면 좋습니다.
 
예전에 이런 광고도 있었죠.
“비트 박스를 잘하려면?” “북치기, 박치기만 잘하면 됩니다”
 
“그럼 PLINQ 개발자가 되기 위해서는?” “AsParallel() 만 잘하면 됩니다.”
 
맞습니다. Parallel Extension Library 의 AsParallel() 확장 메서드(Extension Methods) 만 알면 당신도 이제 병렬 처리 개발자입니다. 이전 포스트의 PLINQ 예제에서 처럼 AsParallel() 만 붙이면 그것을 PLINQ 라고 부릅니다^^ (병렬 처리를 위한 확장 메서드와 옵션은 더 많이 존재합니다 )
 
아래는 AsParallel() 의 예 입니다.
private static void ParallelSort(List<Person> people)
{
       var resultPerson = from person in people.AsParallel()
                                    where person.Age >= 50
                                    orderby person.Age ascending
                                    select person;
 
       foreach (var item in resultPeople) { }
}
 
하지만, 무조건적인 병렬 처리는 오히려 성능을 저하시킬 수 있습니다. 특히 PLINQ 를 사용하는 병렬 처리는 .NET Framework 내부적으로 쿼리 분석(Query Analysis) 과정을 거치게 됩니다. 각각 프로세서(Processor) 에 분배된 데이터는 또 분배되고, 최적화가 가능할 때까지 계속적으로 분배됩니다. 마치 세포 분할이 일어나는 것처럼 말이죠.
 
최소한 병렬 처리를 위해서 데이터에 대한 이해와 추측이 가능해야 합니다.
 
1.     추측 가능한 데이터의 양
2.     추측 가능한 데이터의 내용
3.     추측 가능한 데이터 처리 시간
 
이러한 최소한의 예측 작업을 하지 않는다면, 오히려 PLINQ 를 이용할 때 성능은 저하될 수 있습니다. 예를 들어, 평균 데이터의 양이 2개라고 가정한다면, PLINQ 의 쿼리 분석(Query Analysis) 작업은 오히려 성능 저하의 요인이 됩니다. PLINQ 쿼리 분석(Query Analysis) 에 의해 두 번째 프로세서(Processor) 의 사용량이 많다고 판단된다면, 병렬 작업은 의미가 없어지고 오히려 성능을 저하시킬 테니까요. ‘쿼리 분석(Query Analysis) 작업이 눈 깜빡 거리는 시간(1/40(0.025)초) 이라고 가정한다면, 만 건의 쿼리 분석(Query Analysis) 작업 시간은 250초’가 될 테니까요.
 
최근 대부분의 사용자들의 컴퓨터의 사양이 코어2 로 업그레이드 되고 있습니다. CPU 제품에 따라 코어에 대한 아키텍처가 다르지만, 기본적으로 이들 제품은 하나의 컴퓨터에 CPU 가 두 개인 제품들입니다. 인간과 비교하자면 뇌가 두 개인 사람인데 그다지 상상해서 떠올리고 싶지 않네요^^.
 
컴퓨터는 CPU 두 개를 보다 효율적으로 이용하기 위해 바로 Parallelism Processing(병렬 처리)를 하게 됩니다. 하나의 CPU 의 성능을 향상시키는 방법이 아닌, 두 개의 CPU 에게 작업을 할당함으로써 데이터의 처리 성능을 극대화 시키게 됩니다. 우리에게 익숙한 운영체제인 윈도우(Windows) 의 멀티 쓰레딩(Multi Threading) 을 생각하면 병렬 처리(Parallelism Processing) 는 그렇게 어려운 개념은 아닙니다.
 
[그림1] 어쨌든 뇌가 두 개 (여기에서 참조)
 
원래 오픈 소스 프로젝트로 Parallel Extension 프로젝트를 CodePlex 에서 본 기억이 있는데, 지금은 링크의 주소를 찾을 수 가 없네요. 구글을 통해 “Parallel Extension” 을 검색하시면, .NET 에서의 Parallel Programming 의 흔적을 찾아볼 수 있습니다.
 
우선 아래의 Person 클래스를 작성하여 테스트에 사용할 것입니다.
 
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 
 
General~
 
코어(Core) 하나로 작업할 경우, 개발자는 아무것도 염려 하지 않아도 됩니다. 그 동안 우리가 배웠던 대로 코드를 작성하기만 하면 됩니다. 병렬 처리에 대한 고민을 하지 않고 개발한 코드라면 모두 이 범주에 속하겠네요. 이러한 방법은 가장 보편적으로 작성할 수 있습니다.
 
private static void GeneralSort(List<Person> people)
{
       List<Person> resultPeople = new List<Person>();
       foreach (Person person in people)
       {
             if (person.Age >= 50)
                    resultPeople.Add(person);
       }
 
       resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
 
       foreach (var item in resultPeople) { }
}
 
List<Person> 개체를 파라메터로 넘겨주고, Person 중에 Age 가 50이 넘는 개체를 정렬하는 코드입니다.
바로 이 코드를 병렬 처리를 하고자 합니다. 이 코드를 병렬 처리를 하고자 한다면 코드의 양은 훨씬 늘어나고, 복잡한 처리를 해야 합니다.
 
 
Manual Parallelism
 
일반적으로 데이터의 처리를 병렬 처리로 전환하기 위해서는 쓰레드(Thread) 를 사용합니다. 쓰레드(Thread) 가 생성이 되면 커널 또는 물리적인 프로세서에 의해 의해 유휴 상태 또는 처리가 가능한 코어(Core) 로 작업이 할당되어 다중 작업(Multi Process) 을 가능하게 됩니다.
 
이러한 방법의 병렬 처리는 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 비동기 작업을 합니다.
 
private static void ThreadSort(List<Person> people)
{
       var resultPeople = new List<Person>();
       int partitionsCount = Environment.ProcessorCount;
       int remainingCount = partitionsCount;
       var enumerator = (IEnumerator<Person>)people.GetEnumerator();
       try
       {
             using (var done = new ManualResetEvent(false))
             {
                    for (int i = 0; i < partitionsCount; i++)
                    {
                           ThreadPool.QueueUserWorkItem(delegate
                           {
                                 var partialResults = new List<Person>();
                                 while (true)
                                 {
                                        Person baby;
                                        lock (enumerator)
                                        {
                                              if (!enumerator.MoveNext()) break;
                                              baby = enumerator.Current;
                                        }
                                        if (baby.Age >= 50)
                                        {
                                              partialResults.Add(baby);
                                        }
                                 }
                                 lock (resultPeople) resultPeople.AddRange(partialResults);
                                 if (Interlocked.Decrement(ref remainingCount) == 0) done.Set();
                           });
                    }
                    done.WaitOne();
                    resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
             }
       }
       finally
       {
             if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose();
       }
 
       foreach (var item in resultPeople) { }
}
 
중요한 부분은 추출된 데이터의 정렬(Sort) 작업입니다. 이 작업을 하기 위해서는 모든 쓰레드(Thread) 의 작업이 끝나야 합니다. 만약 모든 쓰레드(Thread) 가 종료되지 않은 시점에서 정렬 작업을 하게 되면, 과연 정렬된 데이터를 신뢰할 수 있을까요?? ( 왜 그런지는 여러분의 상상에 맡기도록 합니다. )
 
정렬 작업을 하기 전 ManualResetEvent 의 WaitOne() 메서드를 호출하여 모든 쓰레드(Thread) 의 WaitHandle 이 작업이 신호를 받을 때까지(동기화 작업) 기다려야 합니다. 예를 들어, 두 개의 쓰레드(Thread) 가 생성 되고 첫 번째 쓰레드는 이미 작업을 종료하였지만, 두 번째 쓰레드는 아직 작업이 완료되지 않았다면, 작업을 마친 모든 쓰레드(Thread) 는 가장 늦게 처리가 완료되는 쓰레드를 기다려야 정렬 작업을 진행할 수 있습니다.
 
마지막으로, 위의 코드의 병렬 처리 작업은 성능에 한계가 있습니다. 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 작업을 분배하는 방식이기 때문에, 병렬 처리 작업의 성능은 곧 프로세서(Processor) 개수가 될테니까요!
 
 
Parallel Extension
 
C# 4.0 은 병렬 처리를 하기 위해 코드의 양을 획기적으로 줄일 수 있습니다.
 
private static void ParallelSort(List<Person> people)
{
       var resultPerson = from person in people.AsParallel()
                                    where person.Age >= 50
                                    orderby person.Age ascending
                                    select person;
 
       foreach (var item in resultPeople) { }
}
 
LINQ 식을 사용하여 데이터 처리와 정렬 작업을 간단하게 할 수 있습니다. 감격이네요^^ 바로, .NET Framework 4.0 의 Parallel Extension 을 사용하여 LINQ 처럼 사용하는 것을 PLINQ 라고 합니다.
 
Q : foreach (var item in resultPeople) { } 코드를넣었나요?
 
A: 동일한 테스트를 하기 위함입니다. LINQ 식은 내부 구조의 특성상 “쿼리식”에 불과합니다.
보다 자세한 내용은 필자의 블로그를 참고하세요.
 
Parallel Extension 은 Manual Parallelism 보다 더 복잡하고 좋은 성능을 낼 수 있는 알고리즘으로 구현이 되어 있습니다. 그렇기 때문에 아무리 많은 코어를 가진 컴퓨터에서 동일한 테스트를 한다고 하여도 결코 Manual Parallelism 은 Parallel Extension 의 병렬 처리 성능을 기대할 수 없습니다.
 
이제 살며시 그 내부 구조도 궁금해 집니다. (다음에 계속…)

.NET Framework 4.0 의 특징

.NET Framework 2009. 2. 8. 15:54 Posted by POWERUMC

.NET Framework 4.0 은 Visual Studio 2010 에 포함되는 최신 프레임워크입니다. 이전에는 .NET Framework 의 새로운 특징이라면 .NET Framework 의 기능 향상과 안정성, 그리고 기능 개선이었습니다. 하지만 .NET Framework 4.0 은 탄탄한 Based .NET Framework 를 통해 여러 가지 새로운 변화를 가져옵니다.

이제는 .NET Framework 가 Win32 API 와 같이(Win32 API 와 비교하기 적절하지는 않지만…) 라이브러리의 집합이 아닌, 어플케이션의 차원에서 보다 견고하고 세련된 빌딩(Building) 을 할 수 있는 진정한 프레임워크로 거듭납니다. .NET Framework 4.0 의 특징을 살펴 봅니다.

Base Class Library 개선

  • MEF(Managed Extensibility Framework)
    • 확장성이 쉬운 선언과 사용
    • 런타임 확장 모니터링
  • 데이터 구조 추가
    • BigInteger & CodeplexNumber
    • Tuple, SortedSet
  • IO 개선
    • 메모리 매핑 파일
    • 모델 해제 통일

MEF 는 어플케이션과 컴포넌트의 재사용성을 높이기 위한 새로운 라이브러리입니다. 확장성 있는 어플케이션과 프레임워크를 만들기 위해 MEF 가 그 대안이라고 제시하고 있습니다. 이 프로젝트는 현재 CodePlex 를 통해 Preview 4 가 릴리즈되었고, 여기에서 다운로드 받을 수 있습니다.

또한 64 비트 프로그래밍을 위한 새로운 데이터 구조가 추가 되었습니다. 64비트 컴퓨팅 시대에 맞춰 .NET Framework 의 Int32 가 한계였다면, 이제는 그보다 더 큰 비트 연산을 할 수 있는 데이터 구조와 새로운 데이터 구조가 추가 되었습니다.

Parallel Computing

  • Task Parallel Library (TPL)
    • 수평적인 병렬 작업의 실행
    • 최대 효율을 위한 Stealing 알고리즘 작업
    • 상위 레벨을 추상화 ( 더 이상 스레드의 지식이 필요 없다 )
  • Parallel Linq (PLINQ)
    • 선언적인 데이터 병렬처리(초점은 ‘무엇’, ‘어떻게’가 아니다)
    • LINQ to Object 를 사용하여 단순한 병렬 처리
  • Coordination Data Structures (CDS)
    • 병렬 처리를 쉽게 하기 위한 공통 구조

다중 코어 프로세서의 사용을 극대화 하기 위해 이제는 병렬 처리를 위해 스레드를 생성하여 동기화 하는 과정을 더 이상 고민하지 않아도 됩니다. 병렬 처리를 하기 위한 확장 메서드를 제공하며, LINQ 식을 통해 데이터의 병렬 처리가 무척이나 쉬워졌습니다. 이제는 ‘어떻게’가 아닌, '무엇을’ 병렬 처리 할 것인지만 생각하면 됩니다.

.NET Framework Client

  • Windows Presentation Foundation
    • 클라이언트 프로파일(Client Profile)
    • 비지니스 컨트롤에 초점
    • 실버라이트 시너지 효과
    • Windows 7 지원 (멀티터치 등)

ADO.NET 4.0

  • Entity Framework v2
    • Code-Fiirst 개발 지원
    • TDD 지원
    • 외래키(Foreign Key) 지원
    • Lazy 로딩

.NET Framework 3.5 SP1 에 등장한 Entity Framework 의 차기 버전입니다. 이전 버전은 기본 키를 중심으로 한 Entity Data Model 이였다면 새로운 버전에서는 외래 키도 지원하게 되었습니다. 또한, TDD(Test-Driven-Development) 을 지원하며, Code-First 방식의 개발도 지원하게 되었습니다.

ASP.NET 4.0

  • ASP.NET Dynamic Data 개선
  • ASP.NET MVC
  • MVC 에 ASP.NET Dynamic Data 지원
    • 데이터 중심으로 뷰와 커스텀 컨트롤 만들기 쉽게
  • CSS, ID, ViewState 컨트롤이 더 좋아진 ASP.NET
  • 확장할 수 있는 캐싱 프레임워크(Caching Framework)

ASP.NET 4.0 의 가장 큰 매력은 바로 ASP.NET MVC 의 통합입니다. 그 동안 Postback, ViewState 기반의 빠른 생산성이 ASP.NET 의 핵심이었습니다. 하지만, ASP.NET MVC 를 통해 Form 기반에서도 빠른 생산성을 향상시켰습니다. 또한, MVC 를 구현하기 위해 많은 코드와 분리 작업을 자동화 할 수 있는 템플릿을 지원합니다.

Velocity

  • .NET 을 위한 분산 캐싱
  • ASP.NET 의 Session State Provider
  • 유연하고, 서로 다른 캐싱 모델
    • Partitioned
    • Replicated
    • Local

.NET 이 지원하는 가장 대표적인 분산 캐싱이 ASP.NET 의 Session State Provider 입니다. 그렇지만 Session State Provider 의 분산 캐싱 능력에는 한계가 있으며, 이러한 한계를 극복할 수 있는 프레임워크가 바로 Velocity 입니다. 캐싱을 분산 처리 하기 위해 많은 고민을 해야 하며, 이러한 분산 캐싱 환경에서 빠른 응답성, 확장성, 고사용성을 높였습니다.

엔터프라이즈 어플케이션 환경에서 많은 관심을 보일 것이며, 특히 요즘 새롭게 대두 되고 있는 SaaS 나 Cloud Computing 을 위해 빠져서는 안될 핵심 기술이 될 것입니다.

Windows Workflow & Communication Foundation

  • 완전 선언적인 서비스
  • 워크플로우 개선
    • 프로그래밍 모델 개선
    • 새로운 플로우차트 모델 스타일 & 확장 활동 팔레트
    • 워크플로우 규칙 통합
    • 디자이너 경험 향상
    • 상당한 성능 향상
    • 상호 메시지
  • WCF 개선
    • Duplex 내구성
    • In-process Channel
    • WS-Discovery & UDP Channel

ADO.NET Data Services

  • 관계형 데이터 지원
  • ‘오프라인’ 상태 지원

ADO.NET Data Services 도 .NET Framework 3.5 SP1 에서 사용하기 위해 몇 가지 추가 작업이 필요하였으며, 보다 자세한 내용은 여기를 참고 하십시오.

특히, 이 버전에서는 Data Services 의 오프라인을 지원합니다. 오프라인에서도 ADO.NET Services 의 사용은 수 많은 외부 시스템과 연동 시에 서비스의 일관성을 유지해 줄 것입니다.

ASP.NET AJAX

  • 자바스크립트 UI 템플릿과 데이터 바인딩
  • AJAX 컨트롤 툴킷 개선
  • DOM Selection, 애니메이션 등

ASP.NET AJAX 는 요즘 가장 인기 있는 AJAX 프레임워크인 JQuery 와 통합하게 됩니다. JQuery 를 사용하고 인텔리센스를 지원하기 위해 몇 가지 수작업이 있었다면, 이제는 그러한 작업 없이도 JQuery 의 고급 기능을 ASP.NET AJAX 에서 사용할 수 있습니다.