더 이상 설정 파일을 매번 변경하지 말자!”

 웹 및 윈도우 응용프로그램을 개발할 때 응용프로그램의 각종 설정 정보를 저장하기 위해 설정 파일을 사용합니다. 이 설정 파일은 데이터베이스 연결 문자열, 디버그 설정, 인증, 권한, 파일 서버의 경로와 같은 응용프로그램에 필요한 설정 정보를 저장하게 됩니다. 간단히 나열하긴 했지만 실제 환경에서의 설정 파일은 상당히 복잡하게 구성되어있는 경우가 허다합니다. 여러분이 지금 개발중인 응용프로그램이 아주 간단한 테스트용 프로그램이 아니라 실 운영을 목적으로 하는 프로그램이라면 최소한 로컬 PC와 운영 서버가 필요하게 됩니다. 로컬 PC와 운영 서버의 환경이 100% 동일할 수 없기 때문에 설정 파일 또한 서로 다를 확률이 아주 높습니다. 단 두 대의 컴퓨터라면 수동으로 설정 파일을 변경할 수도 있겠지만 개발 및 운영환경이 로컬 PC, 개발 서버, 테스트 서버, 스테이징 서버, 운영 서버로 구성되어 있다면 수동으로 설정 파일을 관리하는 것은 스트레스 그 자체일 것입니다.

 

이를 해결하기 위해 ASP.NET 4.0에서는 설정 파일 변환기능(Configuration Transformation)을 제공하고 있습니다. 사실 설정 파일 변환기능은 ASP.NET 4.0의 기능이라기 보다는 Visual Studio 2010의 기능입니다. 이것이 뭐가 중요하냐 라고 생각할 수 도 있지만 Visual Studio 2010의 기능이기 할 수 있는 일이 더 많아진다는여기까지 궁금증을 유발 시키고, 이 글의 중반부에서 다시 설명 드리겠습니다.^^

 

Web.confg 변환하기

우선 웹 프로젝트에서 설정파일을 변환하는 기능에 대해 알아보도록 하겠습니다. 다음은 일반적으로 볼 수 있는 Web.config 파일입니다.

 

Web.config

<?xml version="1.0"?>

<configuration>

  <connectionStrings>

    <add name="DatabaseConnection" connectionString="ConnectionString"/>

  </connectionStrings>

</configuration>

 

아래에 있는 두 설정 파일은 진짜 설정파일이 아니라 Web.config 파일이 배포될 때 어떻게 변해야 할지를 지정하는 XML 파일에 불과합니다. 구문을 간단히 살펴보면 변환 설정은 XML-Document-Transform 네임스페이스에 정의된 XML 특성을 사용하여 지정하게 되며 이 네임스페이스는 xdt Prefix에 매핑 됩니다. 이어서 나오는 connectionString 요소는 Web.config와 동일하지만 Transform 어트리뷰트와 Locator 어트리뷰트가 더 있는 것을 볼 수 있습니다.

 

Locator는 자신에 할당된 값에 해당하는 요소를 선택하며 Transform Locator가 선택한 요소에 대해 수행할 작업을 지정하게 됩니다. 좀더 자세히 살펴보면 xdt:Locator=”Match(name)” Web.config 파일에서 name 어트리뷰트가 “DatabaseConnection”인 요소를 찾습니다. 그 다음 xdt:Transform SetAttributes를 통해 변환 파일에 설정되어 있는 모든 어트리뷰트를 Locator가 선택한 요소에 적용하게 되는 것입니다. SetAttributes에는 매개변수를 지정할 수 있는데 콤마(,)를 구분 자로 하여 어트리뷰트 이름을 전달할 수 있습니다. “SetAttributes(connectionString)”처럼 작성하게 되면 지정된 어트리뷰트에 해당하는 값만을 업데이트 하게 됩니다.

 

다음은 위의 구문을 사용하여 생성한 설정 변환 파일입니다.

 

Web.Debug.config

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

    <connectionStrings>

        <add name="DatabaseConnection"

          connectionString="DebugConnectionString"

          xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>

    </connectionStrings>

</configuration>

 

Web.config와 동일하나 connectionString의 값이 DebugConnectionString입니다.

 

Web.Release.config

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

    <connectionStrings>

        <add name="DatabaseConnection"

          connectionString="ReleaseConnectionString"

          xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>

    </connectionStrings>

</configuration>

 

Web.config와 동일하나 connectionString의 값이 ReleaseConnectionString입니다.

 

설정파일 변환에 대한 자세한 정보는 http://msdn.microsoft.com/ko-kr/library/dd465326.aspx를 참고 하세요.

 

변환 파일 생성이 완료되었으므로 배포를 통해 파일이 변환되는지 확인해 보도록 하겠습니다. 프로젝트의 컨텍스트 메뉴에서 [Publish] 메뉴를 선택하여 웹 게시를 수행합니다. 웹 게시 대화상자에서 게시 방식을 File System으로 설정하고 적절한 폴더를 선택한 다음 게시를 수행합니다. File System으로 설정하는 이유는 그냥 테스트가 쉽도록 하기 위함입니다.

 

 

Figure 1 One-Click 배포 대화상자

 

One-Click 배포에 관한 자세한 정보는 http://msdn.microsoft.com/ko-kr/library/dd465337.aspx를 참고 하세요.

 

이상 없이 배포가 완료되었으면 배포 경로에 Web.config 파일이 있는 것을 볼 수 있으며 이 파일을 편집기를 통해 확인해 보면 connectionString ReleasConnectionString으로 변경된 것을 확인할 수 있습니다. 솔루션 구성(Solution Configuration) Debug로 변경한 다음 다시 One-Click 배포를 수행하면 connectionString의 값이 DebugConnectionString로 변경되는 것을 확인할 수 있습니다.

 

설정 파일 변환에 의해 생성된 Web.config

<?xml version="1.0"?>

<configuration>

  <connectionStrings>

    <add name="DatabaseConnection" connectionString="ReleaseConnectionString"/>

  </connectionStrings>

</configuration>

 

설정파일 변환에 대해서 알아 봤으니 이제 우리의 실 개발환경에서 어떻게 활용하면 좋을까에 대해 고민해 보겠습니다. 지금 진행중인 프로젝트의 서버 구성이 로컬 pc, 개발서버, 스테이징 서버, 운영서버로 구성되어 있다고 가정하면, 각 서버에서 사용하는 DB 연결 문자열과 파일 서버의 경로가 모두 다를 경우 각 서버에 배포할 때마다 해당되는 값들을 수작업으로 변경해야 합니다. 이런 불편하고 비 합리적인 작업을 해결하기 위해 설정 파일 변환이 나온 것이며 이를 실제로 적용해 보도록 하겠습니다.

 

먼저 솔루션 구성(Solution Configuration)을 추가합니다. [Build] -> [Configuration Manager]를 선택하면 Configuration Manager 대화상자가 표시되며 여기서 Active solution configuration 콤보 박스의 <New…>항목을 선택합니다.

 

Figure 2 새로운 솔루션 구성 생성

 

그러면 New Solution Configuration 대화상자가 표시되며 Name 텍스트 박스에 개발서버를 나타내는 Development를 입력하고 설정을 복사해올 대상을 Debug로 선택합니다. 이렇게 하는 이유는 기본 설정을 Debug에서 상속 받겠다는 것이고 추가적인 사항만 변경하겠다는 의미입니다. 또한 개발 서버는 운영환경이 아닌 개발환경이므로 Debug의 설정 내용을 복사하는 것입니다.

 

Figure 3 새 솔루션 구성 대화상자

 

<New…>를 선택하여 스테이징 서버를 의미하는 Staging과 실 운영 서버를 의미하는 Operation을 추가합니다. 이 두 설정은 운영환경이므로 Release로부터 설정 정보를 복사하도록 합니다.

 

설정 추가가 완료되면 Web.config 파일에서 마우스 오른쪽 버튼을 클릭하여 [Add Config Transform]이라는 메뉴를 선택합니다. 이 메뉴는 솔루션 구성(Solution Configuration)에 추가된 모든 항목에 대해 변환파일을 생성해 줍니다.

 

Figure 4 설정파일 변환 자동 생성

 

변환 파일이 모두 생성되면 솔루션 탐색기의 Web.config는 다음처럼 구성됩니다. 간단하고 멋지지 않습니까? 마지막으로 추가된 변환 파일에 DB 연결 문자열과 파일 서버의 경로를 변경해 주고 저장합니다.

 

Figure 5 생성된 변환 파일들

 

이제 모든 준비가 완료되었습니다. 오늘은 스테이징 서버에 배포를 하는 날입니다. 솔루션 구성(Solution Configuration) Staging로 변경하고 One-Click 배포를 수행하기만 하면 추가 작업 없이 설정 파일이 변경되고 배포가 완료됩니다.

 

Figure 6 솔루션 구성 선택

 

App.config 변환하기

일반 응용프로그램(Console, WinForm, WPF )도 설정 파일이 변환이 가능할까? 기본적으로는 안 됩니다. 안 되는데 뭣 하러 이 글을 쓰는 것일까? 기본적으로 안 되는 것이지 불가능한 것은 아닙니다. 기본적으로 안되지만 불가능하지 않다는 것은 아무래도 웹 프로젝트에서 보다 조금 번거로운 작업이 있다는 것을 의미합니다. 그래도 많이 어렵거나 번거롭지는 않습니다. 매번 설정 파일을 변경하는 것 보다는

 

그렇다면 어떻게 가능한 것일까? 서두에서 꺼내다 만 이야기가 있습니다. 설정 파일 변환은ASP.NET 4.0이 제공하는 것이 아니라 Visual Studio 2010이 제공하는 기능이라고. 고로 Visual Studio 2010을 사용하기만 하면 어떤 설정파일이던 모두 변환할 수 있다는 말입니다.

 

여기서는 콘솔 응용프로그램을 사용합니다. WinForm, WPF 모두 동일합니다. 파일 변환 기능은 프로젝트 파일이 어떻게 구성되어 있느냐에 따라 결정됩니다. 일단 아래의 코드를 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 설정 이후에 추가 합니다.

 

설정 파일 변환을 가능하게 하기 위한 XML

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">

<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />

<ItemGroup>

    <AppConfigWithTargetPath Remove="app.config" />

    <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">

    <TargetPath>$(TargetFileName).config</TargetPath>

    </AppConfigWithTargetPath>

</ItemGroup>

</Target>

 

빌드 시 Microsoft.Web.Publishing.Tasks.dll 어셈블리를 사용하여 파일 변환을 수행합니다.  app.config을 소스로 하고 app.솔루션구성.config 파일 즉, app.debug.config 같은 파일을 변환 파일로 사용하여 변환을 하게 됩니다.

 

프로젝트 파일은 프로젝트 언로드(Unload)이후에 편집할 수 있습니다.

 

프로젝트 파일을 변경하였으면 추가 적인 솔루션 구성(Solution Configuration)을 추가 합니다. 여기서는 test 설정 하나만 더 추가 하였습니다. 그 다음 변환 파일을 추가하기 위해 app.config에서 마우스 오른쪽 버튼을 클릭하여 [Add Config Transform]를 선택하려고 하면 [Add Config Transform] 메뉴가 없다는 것을 알게 될 것입니다. 아쉽게도 이 기능은 Web 프로젝트에서만 제공되는 것 같습니다. 자동으로 안되면 어떻게 할까요? 그냥 수동으로 추가해 버리면 그만입니다.

 

수동으로 app.debug.config, app.release.config, app.test.config를 추가 합니다. 자 이제 빌드를 위한 모든 준비가 완료 되었습니다. 솔루션 구성(Solution Configuration) test로 하고 빌드를 수행한 다음 출력 폴더의 ConsoleEx.exe.config(콘솔 프로젝트의 실행 파일 명이 ConsoleEx.exe)을 확인해 보면 설정 파일 변환이 정상적으로 동작 했음을 볼 수 있습니다.

 

번 외로 진정한 개발자라면 깔끔한 코드와, 들여쓰기, 단 하나의 경고도 무시할 수 없는 깐깐함에 더해 멋진 솔루션 구성도 생각해 볼 수 있습니다. 앞에서 만든 프로젝트 구조를 살펴보면 다음처럼 설정파일이 웹 프로젝트와는 다르게 모두 나열되어 있는 것을 볼 수 있습니다.

 

Figure 7 정리되지 않은 설정 파일들

 

설정 파일을 변환하는 데는 아무런 문제도 되지 않지만 성격상!! 이를 고쳐 볼까 합니다. 간단합니다. 다시 한번 프로젝트 파일을 언로드(Unload)하고 파일을 열어보면 설정파일이 있는 ItemGroup이 다음처럼 구성된 것을 볼 수 있습니다.

 

변경 전 app.config

<ItemGroup>

    <None Include="app.config" />

    <None Include="app.debug.config"/>

    <None Include="app.release.config"/>

    <None Include="app.test.config"/>

</ItemGroup>

 

여기서 ItemGroup의 각 하위 요소에 다음 코드처럼<DependentUpon>App.config</DependentUpon>와 같은 자식 요소를 추가하면 Web 프로젝트의 설정 파일처럼 깔끔하게 하위 노드로 들어가게 됩니다. DependentUpon 요소는 지정된 요소에 의존하는 구조로 구성된다는 의미 입니다.

 

변경 후 app.config

<ItemGroup>

    <None Include="app.config" />

    <None Include="app.debug.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

    <None Include="app.release.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

    <None Include="app.test.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

</ItemGroup>

 

Figure 8 깔끔하게 정리된 설정 파일들

 

그다지 중요하지 않아 보일지는 몰라도 이런 습관을 들이는 건 썩 훌륭한 습관이지 않나 하는 생각을 해 봅니다. 아니면 말고..

지금까지 닷넷 응용프로그램에서 설정 파일을 변환하는 방법에 대해 알아 봤습니다. 최초 한번의 고생으로 배포 때 마다 행복할 수 있다면 상당히 훌륭하고 편리한 기능이라고 생각됩니다. 귀찮아 말고 적용해 보면 생각 보다 좋다는 것을 느낄 수 있을 것입니다.

 

더 이상 매번 설정 파일을 변경하지 말자!

 

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

Part1, 2에서 정규표현식을 어떻게 사용하는지에 대해 알아봤습니다. 이 글에서는 Regex 클래스를 사용하는 다양한 방법들에 대해 성능테스트를 해 보고 어떻게 하면 최적의 성능으로 정규표현식을 사용할 수 있는지에 대해 알아보도록 하겠습니다.

 

Regex 클래스와 정규표현식 패턴을 사용하여 개발을 해 본 개발자들은 수행성능에 대해 불평을 하곤 합니다. 왜 이렇게 느린 거야? 라고 말입니다. 하지만 왜 성능이 좋지 않은지 쉽게 진단할 수 없습니다. 정규표현식의 수행속도가 느린 이유는 크게 두 가지로 볼 수 있습니다. 첫 번째는 당연히 정규표현식 패턴 자체에 문자가 있는 경우며, 두 번째는 Regex 클래스를 사용하는 방식이 잘못 되었기 때문입니다. .NET 프레임워크의 정규표현식 개체 모델의 핵심은 Regex 클래스입니다. 다시 말해, Regex 엔진을 사용하는 방식이 정규표현식의 성능에 영향을 주는 핵심 요인인 것입니다.

 

정규표현식 패턴 자체에 대한 성능은 그 분량이 방대하여 이 글에서 다루지 않으며 정규표현식 관련 서적을 참고 하십시오.

 

인스턴스 메서드와 정적 메서드의 성능 차이

Regex 클래스의 인스턴스 메서드를 호출하고 싶다면, 반드시 Regex 클래스의 인스턴스를 생성해야 합니다. 이전 강좌에서 설명하였듯이 Regex 클래스의 인스턴스를 생성 할 때는 생성자 매개변수로 정규표현식 패턴을 전달해야 합니다. 하지만 Regex 개체가 한번 생성되면 생성자 매개변수에 지정된 정규표현식을 변경할 수 없습니다. 이는 강력하게 결합(tightly coupling)되어 있다고 말할 수 있으며, 새로운 정규표현식을 사용하기 위해서는 새로운 Regex 개체를 생성해야만 합니다.

 

이 문제를 해결하기 위해 Regex 클래스는 정적 메서드를 제공하고 있습니다. 아시다시피 정적 메서드는 인스턴스를 생성할 필요가 없으므로 불필요한 인스턴스의 생성으로 인한 비용을 줄일 수 있게 됩니다.

 

static void Run2(string pUrl)

{

   //비교 대상 입력 자료

    string[] urls = { 

                        "http://taeyo.net",

                        "http://www.taeyo.net",

                        "http://www.taeyo.net/board",

                        "http://taeyo.net/board/board.aspx",

                        "http://taeyo.net/talk/board.aspx?id=12",

                        "http://taeyo.net&",

                        "http://www.taeyo.net%",

                        "http://www.taeyo.net/board@",

                        "http://dayofdays.net",

                        "http://blog.dayofdays.net",

                        "http://blog.dayofdays.net/article",

                        "http://dayofdays.net/article/article.aspx?category=test",

                        "http://dayofdays.net/test-url"                        

                    };

   //인스턴스 메서드를 사용하는 테스트

    Stopwatch sw = Stopwatch.StartNew();

    foreach (string url in urls)

    {

        if (IsValidUrlWidthInstance(pUrl, url))

        {

            Console.WriteLine("{0} is valid", url);

        }

        else

        {

            Console.WriteLine("{0} is not valid", url);

        }

    }

    sw.Stop();

    Console.WriteLine("Instance Elapsed Time :\t{0}", sw.Elapsed);

 

   //정적 메서드를 사용하는 테스트

    sw = Stopwatch.StartNew();

    foreach (string url in urls)

    {

        if (IsValidUrlWidthStatic(pUrl, url))

        {

            Console.WriteLine("{0} is valid", url);

        }

        else

        {

            Console.WriteLine("{0} is not valid", url);

        }

    }

    sw.Stop();

    Console.WriteLine("Static Elapsed Time :\t{0}", sw.Elapsed);

}

 

//Regex클래스의 인스턴스 생성

static bool IsValidUrlWidthInstance(string pUrl, string url)

{

    Regex regex = new Regex(pUrl);

    return regex.IsMatch(url);

}

//Regex 클래스의 정적 메서드 호출

static bool IsValidUrlWidthStatic(string pUrl, string url)

{

    return Regex.IsMatch(url, pUrl);

}

 

static void Main(string[] args)

{

   //단순한 정규표현식

    string pUrl1 = @"^https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?$";

   //복잡한 정규표현식

    string pUrl2 = @"^(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)?((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.[a-zA-Z]{2,4})(\:[0-9]+)?(/[^/][a-zA-Z0-9\.\,\?\'\\/\+&amp;%\$#\=~_\-@]*)*$";

    Console.WriteLine("1. 간단한 정규표현식---------------");

    Run2(pUrl1);

    Console.WriteLine("2. 복잡한 정규표현식---------------");

    Run2(pUrl2);

}

 

위 코드는 단순한 정규표현식과 복잡한 정규표현식을 각각 사용하는 인스턴스 생성 방식과 정적 메서드 호출 방식을 테스트 하기 위한 코드입니다. 실행결과는 다음과 같습니다.

 

1. 간단한 정규표현식---------------

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is not valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Instance Elapsed Time : 00:00:00.0022522

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is not valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Static Elapsed Time :   00:00:00.0029799

2. 복잡한 정규표현식---------------

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Instance Elapsed Time : 00:00:00.0206471

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Static Elapsed Time :   00:00:00.0101541

계속하려면 아무 키나 누르십시오 . . .

 

결과를 보면 단순한 패턴을 사용할 경우 인스턴스를 생성하는 방식과 정적 메서드를 사용하는 방식 중 인스턴스를 생성하는 방식이 0.0007초 더 빠르지만 그 차이는 미미한 것으로 보이며, 동일한 코드를 복잡한 패턴으로 사용할 경우 거의 0.01초 차이로 정적 메서드를 사용한 방식이 빠른 것을 확인할 수 있습니다.

 

이렇게 성능에서 차이가 나는 것은 복잡한 정규표현식 패턴은 opcode로 변경 되는데 더 많은 비용을 필요로 하지만, 정적 메서드 호출은 정규표현식 패턴을 캐시 하기 때문에 그 성능이 향상되는 것입니다. , 인스턴스 메서드의 호출은 매번 정규표현식을 opcode로 변경하는 반면, 정적 메서드 호출은 캐시 된 정규표현식과 다른 패턴이 지정된 경우에만 opcode로 변환하고 캐시에 존재할 경우 캐시에서 opcode를 가져와 사용하게 되는 것입니다.

 

오직 정적 메서드만 캐시가 된다는 것을 기억해 두세요. 캐시의 크기는 Regex.CacheSize 속성을 통해 변경 가능하며 기본 캐시의 크기는 15입니다. 만약 캐시의 크기를 초과하여 사용한다면 최근에 사용되지 않은 캐시를 새로운 정규표현식으로 대체하게 됩니다.

 

다섯 가지 정규표현식 생성 방식 및 성능

지금까지는 인스턴스 메서드와 정적 메서드에 초점을 맞추어 알아보았습니다. 이제 좀 더 성능 향상을 가져올 수 있는 컴파일 된 정규표현식에 대해 알아보도록 하겠습니다.

 

.NET 에서 정규표현식 엔진은 세가지 방식으로 정규표현식을 만들어 낼 수 있습니다.

 

1.      정규표현식 인터프리트 - Regex 개체를 생성하거나 Regex 클래스의 정적 메서드를 호출 할 때(정적 메서드에 지정된 정규표현식이 캐시에서 발견되지 않은 경우) 정규표현식은 opcode로 변환됩니다. 메서드가 호출되면 opcode MSIL로 변환되고 JIT 컴파일러에 의해 실행됩니다. 이 방식을 인터프리트 방식이라 하며 시작 시간이 빠른 반면 대신 실행시간이 길어집니다.

2.      정규표현식 컴파일 - Regex 개체를 생성하거나 Regex 클래스의 정적 메서드를 호출 할 때(정적 메서드에 지정된 정규표현식이 캐시에서 발견되지 않은 경우) RegexOptions.Compiled 옵션을 지정하면 정규표현식은 MSIL로 변환됩니다. 메서드가 호출되면 JIT 컴파일러에 의해 MSIL이 실행됩니다. 컴파일 된 정규표현식은 인스턴스 및 정적 메서드 호출에 모두 사용될 수 있습니다. 컴파일 된 정규표현식은 실행시간이 빠른 반면 시작 시간이 길어집니다.

3.      정규표현식을 별도의 어셈블리로 분리 여러 개의 정규표현식을 별도의 어셈블리에 별도의 클래스로 생성합니다. 어셈블리로부터 MSIL이 로드 되고 JIT 컴파일러에 의해 실행됩니다. 여러 클래스에서 참조를 통해 사용가능하며 시작 시 드는 비용을 런타임에서 디자인 타임으로 옮긴다는 장점이 있습니다. , 런타임 환경에서 시작시간과 실행시간 모두 빠르다는 장점이 있습니다.

 

세가지 방식은 시작시간과 실행시간의 장단점이 존재합니다. 이제 앞에서 설명한 모든 방식을 사용하여 성능을 측정해 보도록 하겠습니다. 다음 코드는 다섯 가지 방식의 성능 측정을 위한 테스트 코드입니다.

 

다섯 가지 방식

1.     인터프리트 방식의 인스턴스 메서드 사용

2.     컴파일 방식의 인스턴스 메서드 사용

3.     인터프리트 방식의 정적 메서드 사용

4.     컴파일 방식의 정적 메서드 사용

5.     별도의 어셈블리를 참조하여 인스턴스 메서드 사용

 

private static void PerformanceTest(string input)

{

    Stopwatch sw = null;

    string pUrl = @"https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?";

 

    //[인스턴스 메서드인터프리트

    sw = Stopwatch.StartNew();

    Regex regex1 = new Regex(pUrl);

    MatchCollection matches1 = regex1.Matches(input);

    foreach (Match match in matches1)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Interpret] Matches Count={0}, Elapsed Time : {1}", matches1.Count, sw.Elapsed);

 

    //[인스턴스 메서드컴파일

    sw = Stopwatch.StartNew();

    Regex regex2 = new Regex(pUrl, RegexOptions.Compiled);

    MatchCollection matches2 = regex1.Matches(input);

    foreach (Match match in matches2)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Compile] Matches Count={0}, Elapsed Time : {1}", matches2.Count, sw.Elapsed);

 

    //[정적 메서드인터프리트

    sw = Stopwatch.StartNew();

    MatchCollection matches3 = Regex.Matches(input, pUrl);

    foreach (Match match in matches3)

    {

    }

    sw.Stop();

    Console.WriteLine("[Static][Interpret], Matches Count={0}, Elapsed Time : {1}", matches3.Count, sw.Elapsed);

 

    //[정적 메서드컴파일

    sw = Stopwatch.StartNew();

    MatchCollection matches4 = Regex.Matches(input, pUrl, RegexOptions.Compiled);

    foreach (Match match in matches4)

    {

    }

    sw.Stop();

    Console.WriteLine("[Static][Compile] Matches Count={0}, Elapsed Time : {1}", matches4.Count, sw.Elapsed);

 

    //[인스턴스 메서드어셈블리

    sw = Stopwatch.StartNew();

    URLRegex urlRegex1 = new URLRegex();

    MatchCollection matches5 = urlRegex1.Matches(input);

    foreach (Match match in matches5)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Assembly] Matches Count={0}, Elapsed Time : {1}", matches5.Count, sw.Elapsed);

 

}

 

다음은 코드를 실행한 결과 입니다.

 

1. 소량 데이터를 사용한 검사---------------

[Instance][Interpret] Matches Count=1, Elapsed Time : 00:00:00.0000213

[Instance][Compile] Matches Count=1, Elapsed Time : 00:00:00.0000086

[Static][Interpret], Matches Count=1, Elapsed Time : 00:00:00.0000139

[Static][Compile] Matches Count=1, Elapsed Time : 00:00:00.0000094

[Instance][Assembly] Matches Count=1, Elapsed Time : 00:00:00.0000094

2. 대량 데이터를 사용한 검사---------------

[Instance][Interpret] Matches Count=1048576, Elapsed Time : 00:00:04.9888389

[Instance][Compile] Matches Count=1048576, Elapsed Time : 00:00:05.6970633

[Static][Interpret], Matches Count=1048576, Elapsed Time : 00:00:04.9711290

[Static][Compile] Matches Count=1048576, Elapsed Time : 00:00:04.1505893

[Instance][Assembly] Matches Count=1048576, Elapsed Time : 00:00:03.9443341

계속하려면 아무 키나 누르십시오 . . .

 

결과는 소량의 데이터를 사용한 경우와 대량의 데이터를 사용한 경우로 나누어지며 다음 그래프를 통해 분석 해 보도록 하겠습니다.

 

당연한 결과이나 소량의 데이터를 사용할 경우 상당히 적은 시간이 소요되며 대량의 데이터를 사용할 경우 최소 3초 이상의 시간이 소요되는 것을 확인할 수 있습니다. 먼저 소량의 데이터를 처리하는 경우를 살펴보도록 하겠습니다.

 

 

 

소량의 데이터를 사용할 경우 별도의 어셈블리를 사용하여 처리하는 것이 가장 빨랐으며 인터프리트 방식의 인스턴스 메서드를 사용하는 것이 가장 느리게 나타났습니다.

 

 

대량의 데이터를 사용할 경우도 별도의 어셈블리를 사용하여 처리하는 것이 가장 빨랐으며 컴파일 방식의 인스턴스 메서드를 사용하는 것이 가장 느리게 나타났습니다.

 

적절한 방식의 선택

결론적으로 별도의 어셈블리를 사용하는 방식이 가장 빠르고 인스턴스를 생성하는 방식이 가장 느렸지만 별도의 어셈블리를 사용하는 경우에는 정적 메서드를 사용하는 것처럼 유연하지는 못합니다. 그래서 닷넷에서 정규표현식의 성능을 최대로 향상시키기 위해서 Regex 를 사용할 때는 다음처럼 하기를 권장합니다.

 

1.     동일한 Regex 개체를 반복적으로 생성하기 보다는 정적 메서드를 사용합니다.

2.     정규표현식 패턴을 사용하는 회수가 적고 패턴이 상대적으로 간단한 경우 인터프리트 방식을 사용합니다.

3.     동일한 정규표현식을 수 많은 곳에서 사용할 경우 성능 최적화를 위해 컴파일 된 정규표현식을 사용합니다.

4.     정규표현식 패턴과 옵션을 변경할 필요가 없으며 최상의 성능을 내기 위해서는 외부 어셈블리로 컴파일 하는 방식을 사용합니다.

 

적절한 방식으로 정규표현식을 사용할 경우 훌륭한 성능으로 패턴 매칭 및 유효성 검사, 검색 등에 사용할 수 있을 것입니다. 정규표현식을 통한 문자열 작업은 직접 사용해 보지 않고는 그 강력함을 알 수 없습니다. 우리 모두 정규표현식을 잘 다루는 개발자가 되어 보자구요^^

 

--

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

컴파일 된 정규표현식 사용하기

이전 강좌의 두 가지 방법으로 복잡한 정규표현식을 자주 적용하다 보면 응용프로그램의 성능이 저하될 수 있습니다. 성능 저하를 최소화 하기 위해서는 정규표현식을 MSIL(Microsoft Intermediate Language)로 컴파일 하여 성능을 향상시킬 수 있으며, 이는 Regex 개체를 생성할 때 생성자 매개변수로 RegexOptions.Compiled 옵션을 지정하여 컴파일 되도록 설정할 수 있습니다.

 

Regex 개체를 생성할 때, 생성자에 전달한 정규표현식 패턴은 중간 형식(Intermediate Form, opcode)으로 컴파일 됩니다. 런타임은 Regex 개체를 사용할 때 마다 정규표현식 패턴의 중간 형식을 해석(Interpret)하여 대상 문자열에 적용하게 됩니다. 복잡한 정규표현식이 빈번하게 사용되면 이러한 해석 작업이 반복되며, 그것으로 인해 응용프로그램의 성능에 나쁜 영향을 미칠 수 있습니다.

 

중간 형식(Intermediate Form, opcode) MSIL(Microsoft Intermediate Language)가 아님

 

컴파일 된 정규표현식

Regex 개체를 생성할 때 RegexOptions.Compiled 옵션을 설정하면 .NET 런타임이 정규표현식을 중간형식으로 해석하는 대신 MSIL로 컴파일 하도록 강제할 수 있습니다. MSIL은 최초 수행 시 런타임에 의해 네이티브 기계어 코드로 JIT(just-in-time) 컴파일 됩니다. 마치 정규 어셈블리 코드처럼 동작하는 것입니다. 그런 다음 Part 1에서 설명한 것처럼 일반 Regex 개체를 사용하는 방식으로 사용하면 보다 빠른 성능을 기대할 수 있습니다.

 

정규표현식의 컴파일은 성능상의 장점이 있는 반면, 그로 인한 두 가지의 단점도 있습니다. 첫 번째는 JIT 컴파일러가 더 많은 작업을 해야 하므로 JIT 컴파일을 하는 동안 지연이 발생할 수 있다는 것입니다. 두 번째로, 런타임은 컴파일 된 정규표현식을 언로드 할 수 없다는 것입니다. 일반적인 정규표현식과 달리 런타임 가비지 컬렉터는 컴파일 된 정규표현식에 의해 사용된 메모리를 회수하지 않습니다. 그렇기 때문에 컴파일 된 정규표현식은 프로그램이 종료되거나 컴파일 된 정규표현식이 로드 된 응용프로그램 도메인을 언로드 할 때까지는 메모리에 그대로 남아있게 됩니다.

 

컴파일 된 정규표현식을 외부 어셈블리로 내보내기

Regex 클래스의 정적 메서드인 CompileToAssembly는 정규표현식을 컴파일하고 외부 어셈블리로 작성하는 기능을 제공합니다. 이를 사용하면 여러 개의 응용프로그램에서 공유할 수 있는 정규표현식들로 구성된 어셈블리를 생성할 수 있습니다.

 

정규표현식을 컴파일하고 어셈블리로 만들기 위해서는 컴파일 된 정규표현식을 할당하기 위한 regexCompilationInfo 개체들을 저장해 둘 RegexCompilationInfo의 배열을 생성합니다. 그 다음 컴파일 된 정규표현식을 담기 위한 RegexCompilationInfo 개체를 생성합니다. 개체를 생성할 때 다음 매개변수들을 생성자 매개변수로 전달합니다

.

속성명

설명

IsPublic

생성된 정규표현식 클래스가 public인지를 지정하는 bool

Name

클래스 이름

Namespace

클래스의 네임스페이스

Pattern

정규표현식

Options

정규표현식의 옵션을 지정하기 위한 RegexOptions 열거형 값

 

이제 어셈블리를 생성하기 위해 System.Text.Reflection.AssemblyName 개체를 생성하고 Regex.CompileToAssembly 메서드를 통해 생성할 어셈블리의 이름을 나타내는 AssemblyName.Name에 값을 설정합니다. 마지막으로 RegexCompilationInfo 배열과 AssemblyName개체를 매개변수로 Regex.CompileToAssembly 메서드를 실행합니다.

위 과정대로 수행하면 컴파일 된 하나의 정규표현식이 하나의 클래스로 만들어지고 이 클래스들을 포함하는 어셈블리(dll)가 출력(Output) 폴더에 만들어집니다. 각 클래스는 Regex 클래스로부터 파생됩니다.

 

RegexCompilationInfo[] regexInfos = new RegexCompilationInfo[2];

//정규식 클래스 PinRegex를 생성하기 위한 정보 설정

regexInfos[0] = new RegexCompilationInfo(@"^\d{4}$", RegexOptions.Compiled, "PinRegex", "", true);

//정규식 클래스 URLRegex를 생성하기 위한 정보 설정

regexInfos[1] = new RegexCompilationInfo(@"^https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?$", RegexOptions.Compiled, "URLRegex", "", true);

 

//어셈블리 이름을 설정

AssemblyName assemblyName = new AssemblyName();

assemblyName.Name = "MyRegex";

 

//정규식 클래스들을 어셈블리로 기록

Regex.CompileToAssembly(regexInfos, assemblyName);

 

위의 코드를 실행하면 출력(Output) 폴더에 MyRegex.dll 어셈블리가 생성이 되며 이를 사용하기 위해 어셈블리를 참조합니다.

 

그림 1. MyRegex 어셈블리 참조

 

어셈블리에 포함된 정규표현식을 사용하기 위해서는 매개변수(정규표현식) 없이 Regex 개체를 생성하는 것처럼 해당 클래스의 개체를 생성하고 생성된 개체의 IsMatch 메서드를 호출하여 검사를 수행할 수 있습니다. 각 클래스는 Regex 클래스로부터 파생되었기 때문에 정적 IsMatch 메서드도 제공하지만 이 메서드는 부모 클래스인 Regex의 정적 메서드로써 정규표현식을 매개변수로 지정해야 하므로 무시하면 됩니다. 이제 아래 코드와 같이 인스턴스를 생성하고 IsMatch 메서드를 호출하도록 합니다.

 

PinRegex rpin = new PinRegex();

bool b1 = rpin.IsMatch("1234");

URLRegex rurl = new URLRegex();

bool b2 = rurl.IsMatch("http://teayo.net/talk/board.aspx?no=1&page=2");

 

위 코드와 같이 호출하면 Regex 개체를 사용하는 것보다 그 과정이 복잡하지만 위 방식은 여러 번 클래스를 생성하지 않고도 각 개체의 인스턴스 메서드인 IsMatch를 사용할 수 있으므로 불필요한 메모리의 낭비와 성능 문제를 해결 할 수 있습니다. 하지만 정규표현식 패턴과 RegexOptions들이 이미 정해진 채로 어셈블리가 생성되고 이는 추후에 변경할 수 없으므로 유연성이 상당히 떨어진다는 단점도 존재합니다.

 

정규표현식을 사용하기 위한 방법은 Regex 클래스의 인스턴스 메서드를 사용하는 것과 정적 메서드를 사용하는 방법, 컴파일 통해 사용하는 방법, 별도의 어셈블리로 만들어 사용하는 방법이 있으며, 이 들은 모두 적절한 용도가 있기 때문에 .NET에서 제공 하겠지만 어떤 상황에서 어떤 방식을 사용해야 할지는 명확하게 정의되어 있지 않습니다. 이 문제를 해결하기 위해 다음 글에서는 정규표현식 성능 측정 및 최적화에 대해 다루어 보도록 하겠습니다.

 

감사합니다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

응용프로그램을 개발할 때 사용자가 입력한 값이 응용프로그램이 원하는 형식과 구조인지를 파악하기 위해 유효성 검사를 수행합니다. 예를 들면, 사용자가 올바른 전화번호, 주민등록번호, 이메일 주소를 입력했는지 확인할 필요가 있습니다. 닷넷 뿐만 아니라 대부분의 프로그래밍 언어에서는 유효성 검사를 수행하기 위해 정규표현식(Regular Expression)을 사용할 수 있습니다.

 

정규표현식은 특정 패턴이 있는 문자열을 검사할 수 있는 아주 훌륭한 메커니즘을 제공합니다. 그렇기 때문에 정규표현식은 유효성 검사를 하기 위한 가장 좋은 방법 중 하나입니다.

 

우선 입력된 데이터의 구조와 내용에 대한 유효성 검사를 수행하는데 사용될 정규표현식의 문법을 이해해야 합니다. 주변을 둘러보면 정규표현식을 잘 활용하는 개발자들은 그다지 많지 않은 것 같습니다. 정규표현식은 프로그래밍 언어에서뿐만 아니라 각종 에디터 및 워드, 엑셀 등에서 특정 패턴을 검색하거나 교체할 때 상당히 유용하게 사용될 수 있습니다. 정규식에 대한 모든 것을 이 글에서 설명할 수 없기에 http://regxlib.com 이나 MSDN의 정규표현식 언어요소(http://msdn.microsoft.com/ko-kr/library/az24scfc.aspx)를 참고하면 쉽게 정규식을 익힐 수 있을 것입니다.

 

정규표현식 요소

정규표현식은 크게 리터럴(literal)과 메타문자(meta character)로 구분할 수 있습니다. 리터럴은 일치되길 원하는 특정 문자를 말하며, 메타문자는 와일드카드, 그룹, 반복, 범위, 조건 및 또 다른 제어 메커니즘을 제공하는 문자를 말합니다. 다음은 자주 사용되는 메타문자입니다.

요소

설명

.

\n(new line) 문자를 제외한 모든 문자

\d

10진수

\D

10진수가 아닌 문자

\s

공백문자

\S

공백문자가 아닌 문자

\w

알파벳이나 한글 및 숫자 같은 문자

\W

문자가 아닌 것

^

문자열이나 줄의 시작

\A

일치하는 부분이 문자열의 시작에 있어야 함

$

문자열이나 줄의 끝

\z

일치하는 부분이 문자열의 끝에 있어야 함

|

파이프로 구분된 값들 중 일치하는 하나를 찾음

This|That|What This That, What중 하나가 있을 경우 일치하게 됨

[abc]

대괄호 안의 문자 중 일치하는 하나를 찾음

[QwEr] Q, w, E, r 중 하나가 있을 경우 일치하게 됨

[^abc]

^기호 뒤에 오는 문자가 아닌 문자를 찾음

[^QwEr] ab는 찾지만 Qw는 찾지 못함

[a-z]

지정된 문자 범위 내에 있는 문자를 찾음

[A-C] A, B, C를 찾음

( )

정규표현식 내부의 또 다른 부분 정규표현식을 나타냄

?

?앞의 식이 있거나 없을 수 있음

*

*앞의 식이 0번 이상 올 수 있음

+

*앞의 식이 1번 이상 올 수 있음

{n}

{n}앞의 식이 n개인 경우를 찾음

{n,}

{n}앞의 식이 최소한 n개인 경우를 찾음

{n, m}

{n, m}앞의 식이 최소 n개에서 최대 m개인 경우를 찾음

 

더 많은 요소들에 대한 설명은 MSDN 정규표현식 언어요소(http://msdn.microsoft.com/ko-kr/library/az24scfc.aspx)에 자세히 설명되어 있음

 

보다 복잡한 데이터에 대한 유효성 검사를 할 때는 보다 복잡한 정규표현식 문법이 필요합니다. 예를 들어, 최소값을 갖는 숫자 데이터를 검사하는 것은 쉽지만 URL이나 주민등록번호를 검사하는 것은 상당히 복잡합니다. 다음 표는 응용프로그램을 개발할 때 자주 사용하는 정규표현식을 만들어 본 것입니다. 최소한의 문법을 사용하여 구현하였으며 카드번호 및 주민등록번호의 각 자리가 의미하는 복잡한 패턴은 정의되어있지 않습니다.

 

입력 값

정규표현식

설명

숫자

^\d+$

문자가 아닌 숫자

단순비밀번호

^\w{6,8}$

6~8자리까지의 문자(숫자 포함) 비밀 번호를 검사함

신용카드번호

^\d{4}-?\d{4}-?\d{4}-?\d{4}$

4자리 숫자가 4번 반복됨 하이픈(-)은 없을 수도 있음

주민등록번호

^\d{6}-?\d{7}$

6자리 숫자와 7자리 숫자의 가운데에 하이픈(-)이 들어감

이메일

^[\w-]+@([\w-]+\.)+[\w-]+$

이메일 주소를 검사함

URL

^https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?$

URL을 검사함

 

위의 정규표현식 중 그나마 가장 복잡해 보이는 URL을 분석해 보고 정규표현식에 조금 적응을 해 보도록 하겠습니다.

 

URL 정규표현식 패턴(^https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?$) 분석

1.      ^는 문자열이나 줄의 시작을 나타냅니다.

2.      http는 리터럴로 순수 http라는 문자에 대응합니다. 1 2를 함께 보면 입력된 문자는 http로 시작해야 한다는 것을 의미합니다.

3.      s?는 리터럴 s가 있거나 없을 수도 있음을 나타냅니다. http 또는 https로 문자열이 시작된다는 것을 의미합니다.

4.      ://는 단순 리터럴입니다.

5.      ([\w-]+\.)+ \w(문자)나 하이픈(-)이 한번 이상 반복된 다음 마침표(.)가 나옵니다. , www. 또는 w. 또는 mobile-. 등이 될 수 있습니다. 그리고 이것이 괄호로 묶여 있고 +가 있으므로 이 전체가 한번 이상 반복될 수 있습니다. ex) www.dayofdays. 또는 www.taeyo.

6.      [\w-]+는 문자 및 하이픈(-)이 한번 이상 반복될 수 있음을 의미합니다.

ex)www.dayofdays.net 또는 www.taeyo.net

7.      마지막으로 (/[\w-./?&%]*)?$는 문자(\w)나 하이픈(-) 또는 마침표(.), 슬래시(/). 물음표(?), 앰퍼센드(&), 퍼센트(%)중 한가지가 0번 이상(*)올 수 있으며 이 문자열의 앞에 리터럴 /가 오며 이 전체((/[\w-./?&%]*))가 있어도 없어도(?)되며 문자열이나 줄의 끝($)이어야 합니다. /talk/board.aspx?no=1&page=2와 같은 문자열을 검사합니다.

대괄호[] 안에 문자를 지정하는 경우 해당 문자들을 중 하나와 일치하는 값을 찾아내므로 .이나 ?가 역슬래쉬(\)없이 사용되었지만 메타문자가 아닌 일반 문자로 취급됩니다.

 

하나하나씩 분석해 보면 그다지 어렵지 않으며 순차적으로 해석하되 []를 먼저 해석하고 ()를 해석한 다음 전체를 보면 좀 더 쉽게 이해할 수 있습니다.

 

Regex 클래스를 사용하여 유효성 검사 수행

앞에서 정규표현식을 생성하였으므로 이제 Regex 클래스를 사용하여 실제 유효성 검사를 수행해보도록 하겠습니다. Regex 개체는 Regex 생성자에 정규표현식을 매개변수로 전달하여 만들 수 있습니다. 개체를 생성했으면 Regex 개체의 IsMatch 메서드를 사용하여 검사를 수행할 수 있습니다. IsMatch 메서드는 검사할 문자열을 매개변수로 취합니다. IsMatch 메서드는 전달한 문자열이 정규표현식과 일치한다면 true를 반환하고 그렇지 않다면 false를 반환하게 됩니다.

코드는 다음과 같다.

 

public static bool Validate(string regexString, string inputValue)

{

         Regex regex = new Regex(regexString);

         return r.IsMatch(inputValue);

}

 

위 메서드의 실행 결과는 다음과 같습니다.

Validate(“^\d+$”, ”12345”); //true를 반환

Validate(“^\d+$”, ”12345A”); //false 반환

 

다양한 문자열을 검사하기 위해 Regex 개체를 반복적으로 사용할 수 있습니다. 그러나 Regex 개체에 할당된 정규표현식은 생성자를 통해 할당하였고 별도의 속성을 제공하지 않기 때문에 교체할 수 없습니다. Regex 개체를 재사용할 수 없으므로 새로운 정규표현식을 사용하기 위해서는 매번 Regex 개체를 생성해야 합니다. 이는 그다지 훌륭한 방법이 아닐 수 있습니다. 이런 경우 보다 적절한 대안은 IsMatch 메서드의 오버로드로 제공되는 static 메서드를 사용하는 것입니다

.

public static bool Validate(string regexString, string inputValue)

{

         return Regex.IsMatch(regexString, inputValue);

}

 

앞에서 설명한 두 방법은 Regex개체가 사용될 때 마다 런타임이 정규표현식을 중간 형식(Intermediate form, opcode)으로 변경(interpret)한 다음 대상 문자열에 적용합니다. 이로 인한 문제점 및 해결을 위한 방법을 다음 글에서 알아보도록 하겠습니다.

 

감사합니다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

얼마 전까지 엄청난 스트레스를 받는 프로젝트를 진행했습니다. 빨리 끝나기만을 손꼽아 기다렸던 그런 프로젝트였습니다. 하지만 아무리 스트레스를 받고 짜증나더라도 개발에 관련된 일이건 인간관계에 관련된 일이건 배울 것은 있다는 것을 다시 한번 깨닫게 그런 프로젝트였습니다.

그래서 배운 것이 무엇이냐? 바로 다중 모니터를 사용하는 환경을 제어는 방법과 이때 발생되는 문제가 있다는 것입니다.

다중 모니터(Multi Monitor) 제어

일반적인 응용프로그램을 개발할 , 다중 모니터까지 고려할 일은 거의 없습니다. 다중 모니터라 하여 특별한 처리를 필요도 없을뿐더러 운영체제가 알아서 해주기 때문입니다. 하지만 개발할 응용프로그램이 이상의 (Form)으로 구성되고 상호작용을 한다면 얘기가 달라집니다. 개의 폼이 동시에 실행되고 번째 모니터에 번째 폼이 번째 모니터에 번째 폼이 나타나야 한다면 어떻게 처리해야 할까요? 문득 드는 생각은 모니터(Primary Monitor) 특별한 처리를 필요가 없을 것이고, 모니터(Secondary Monitor) 폼을 출력할 경우만 고려하면 것입니다.

이는 그리 복잡하지 않은 로직으로 해결할 있습니다. 번째 폼이 모니터에 출력되어야 경우 번째 폼의 Location 지정해 주면 됩니다. 모니터의 왼쪽 상단 좌표는 모니터의 넓이(width) 0입니다. , SecondForm.Location = new Point( 모니터의 width, 0) 같이 설정하면 됩니다. 하지만 닷넷은 이를 좀더 쉽고 편리하게 처리하기 위해 시스템상의 모니터() 관리하는 Screen이라는 클래스를 제공합니다.

Screen 클래스는 시스템상의 모든 모니터를 알아내는 AllScreens라는 속성을 제공합니다. 속성을 사용하면 현재 멀티 모니터를 사용하고 있는지를 알아낼 있으며 특정 모니터에 폼을 출력시킬 수도 있습니다.

_secondForm = new SecondForm();

Screen[] screens = Screen.AllScreens;

           

if (screens.Length > 1)

{

    _secondForm.Show();

    if (screens[1].Primary == false)

    {

// 위치 넓이를 설정 

        _secondForm.Bounds = screens[1].Bounds;

        // 위치만 설정

        //secondForm.Location = screens[1].Bounds.Location;

    }

}

 

AllScreens 속성을 통해 모든 스크린을 알아낸 다음, 스크린의 개수가 1 이상이면 멀티 모니터를 사용하고 있다고 가정할 있습니다. 0번째 스크린이 모니터이며 1번째 스크린이 모니터일 것입니다. 확실하게 하기 위해 Screen Primary 속성을 사용하여 모니터인지 모니터인지를 확인할 수도 있습니다.  이렇게 모니터가 있다면 Form Bounds 속성에 Screen Bounds 속성을 할당합니다. Bounds속성은 클라이언트 영역(스크롤 , 제목 표시줄 ) 포함한 위치 넓을 값을 포함하고 있으며 번째 폼에 번째 스크린의 Bounds 할당하게 되면 번째 모니터에 차는 폼이 출력됩니다. 만약 위치만 번째 모니터에 지정하고 싶을 경우에는 Location 속성을 사용하면 됩니다.

이렇게 Screen 클래스를 사용하여 다중 모니터를 쉽게 제어했습니다. 하지만 이때 새로운 문제가 발생할 있습니다.

멋대로 표시되는 메시지 박스

번째 폼과 번째 폼에서 각각 메시지 박스를 띄워보면 아무 이상이 없을 것입니다. 폼을 모니터와 모니터로 이동하면서 메시지 박스를 띄워 보아도 아무 문제 없을 것입니다. 그렇다면 번째 폼에서 번째 폼의 메서드를 호출하여 메시지 박스를 출력해 보도면 어떻게 될까요? 우선 번째 폼에 ShowMessage라는 메서드를 만들고 메서드를 번째 폼에서 호출하는 코드를 만듭니다.

//FirstForm.cs

private void btnSendMessage_Click(object sender, EventArgs e)

{

    if (_secondForm != null)

    {

        _secondForm.ShowMessage(txtMessage.Text);

    }

}

 

//SecondForm.cs

public partial class SecondForm : Form

{

    public void ShowMessage(string message)

    {

        //소유자를 설정하지 않으면 메시지 박스는 (primary) 모니터에 표시된다.

        MessageBox.Show(message);

}

}

 

번째 폼의 btnSendMessage 버튼을 클릭하면 번째 폼의 ShowMessage 메서드가 호출되며 어떤 일이 발생하는지 보도록 하겠습니다. 메시지 박스는 모니터에 출력될 것입니다. 그럴 수도 있지 하며 넘어갈 있는 문제 같아 보이지만 개발자가 아닌 일반 사용자들은 버튼을 클릭하면 번째 폼에 어떤 동작이 수행될 것이라 예상하고 있습니다. 메시지 박스는 모니터에 출력되고 메시지 박스가 모달 다이얼로그(ModalDailog)이다 보니 번째 폼은 비활성화 되어 아무런 동작도 수행되지 않게 됩니다. 사용자는 메시지 박스를 보지 못하고 응용프로그램이 죽었다 또는 얼었다고 말할 것입니다. 개발자 입장에서 해결은 해야 하지만 사용자로부터 전달받은 오류의 내용은 그냥 죽었다 또는 얼었다라는 말뿐입니다. 당연히 오류를 재현하기도 힘들어 문제의 원인을 찾기 어려울 것입니다.

부분은 전적으로 개발자의 잘못이며 개발자가 해결해야 부분입니다. 글의 목적이 바로 문제를 해결하는 것입니다.

메시지 박스가 모니터에 뜨는 것일 까요?

MessageBox 클래스는 많은 오버로드(overload) 제공하고 있습니다. 하지만 저를 포함한 대부분의 개발자들은 메시지 매개변수 하나만을 받는 오버로드를 사용하는 경우가 많습니다. 이것이 바로 문제의 원인입니다. MessageBox 번째 매개변수로 소유자(Owner) 지정할 있습니다. 소유자는 MessageBox 소유하게 되는 개체로 보통 메시지 박스를 띄우는 폼이 소유자가 됩니다. MessageBox 소유자를 지정하지 않을 경우 자동으로 소유자를 지정하게 됩니다. 자동으로 소유자를 지정하는데 문제가 될까요? 리플렉터(.NET Reflector) 통해 MessageBox 내부를 보면 소유자를 지정하는 다음과 같은 코드를 있습니다.

else if (owner == IntPtr.Zero)
    owner = UnsafeNativeMethods.GetActiveWindow();

 

소유자가 IntPtr.Zero, , 소유자가 지정되지 않으면 UnsafeNativeMethods.GetActiveWindow() 통해 현재 활성화된 윈도우를 소유자로 지정하게 됩니다. 드디어 문제가 해결되었습니다.

소유자를 지정하지 않은 상태에서 번째 폼의 버튼을 클릭하면 활성화된 윈도우는 번째 폼이 됩니다. 이때 번째 폼에서 메시지 박스를 띄울 것이며 메시지 박스의 소유자는 활성화된 윈도우 , 번째 폼이 되는 것입니다.

문제점이 명확해 졌으니 해결해 보도록 할까요? 그렇습니다. 소유자를 지정하기만 하면 됩니다.

public void ShowMessage(string message)

{

    //메시지 박스의 소유자를 설정하지 않으면 메시지 박스는 (primary) 모니터에 표시된다.

    //MessageBox.Show(message);

    // 문제를 해결하기 위해서는 소유자를 반드시 설정해야 한다.

    MessageBox.Show(this, message);

}

MessageBox 번째 매개변수로 소유자를 반드시 지정하는 습관을 기르는 것이 아주 중요합니다. 이런 습관이 없었기에 이런 아티클을 쓰고 있지만 어쨌든 좋은 하나 알았으니 만족하며 저를 포함한 모든 개발자 분들이 소유자를 지정하는 습관을 길렀으면 합니다.

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

상대경로 알아내기

.NET 2011/01/03 15:45

응용프로그램을 개발하면서 응용프로그램의 시작 위치로부터 상대 경로에 있는 이미지나 특정 파일의 경로를 알아야 할 경우가 있다. 이런 경우를 만났을 때 대부분의 개발자들은 그런 것은 .NET이 기본으로 제공하지 않나? 전에 쉽게 해결했었는데라고 생각할 것이다. 물론 .NET이 이를 위한 기능을 제공하고 있지만 막상 구현해 보면 그리 만만한 작업이 아니라는 것을 알게 될 것이다.

.NET“C:\Windows\System32\msxml4.dll”과 같은 절대 경로를 “windows”라는 디렉터리를 기준으로 “System32\msxml4.dll”이라는 상대 경로로 변환해 주는 메서드를 제공하지 않는다. 정말 그럴까? 아마 그럴 것이다. 그럼 어떻게 상대경로를 알아낼 수 있을까? 해답은 생각보다 싱겁다. 코드를 보면 아하!”하는 생각이 절로 들 수도 있을 것이다. 바로 Uri클래스를 사용하면 된다.

Uri클래스에는 MakeRelativeUri라는 메서드가 있으며 이 메서드는 절대 uri를 매개변수로 받아 상대 uri를 반환해 준다. uri는 슬래쉬를 구분자로 하기 때문에 이를 역 슬래쉬로 변경해 주기만 하면 상대 경로를 알아낼 수 있게 된다.

다음은 상대 경로를 알아내는 전체 소스 코드이다.

public static string GetRelativePath(string path, string basePath)

{

       //path가 절대 경로인 경우에만 처리

       if (System.IO.Path.IsPathRooted(path) == false)

             return path;

 

       //기준 경로가 주어지지 않은 경우 현재 응용프로그램 도메인의 베이스 디렉토리를 기준 경로로 설정한다.

       if (string.IsNullOrWhiteSpace(basePath))

       {

             basePath = AppDomain.CurrentDomain.BaseDirectory;

       }

 

       //역 슬래쉬로 끝나야만 Uri 변경 시 해당 경로를 정확히 인식한다.

       if (basePath.EndsWith("\\") == false)

       {

             basePath += "\\";

       }

 

       Uri baseUri = new Uri(basePath);

       //Uri MakeRelativeUri 메서드를 사용하여 상대 Uri로 변경한다.

       Uri relativeUri = baseUri.MakeRelativeUri(new Uri(path));

 

       //uri는 슬래쉬를 사용하므로 슬래쉬를 역슬래쉬로 변경하여 Uri를 디렉터리 경로 형태로 변경한다.

       string relativeString = relativeUri.ToString().Replace("/", "\\");

       return relativeString;

}

 

먼저 매개변수로 주어진 경로가 절대 경로일 경우에만 처리하기 위해 System.IO.Path.IsPathRooted 메서드를 사용한다. 이 메서드는 매개변수로 주어진 경로가 절대 경로일 경우 true 그렇지 않을 경우 false를 반환한다.  그 다음 basePath를 검사하여 빈 값이라면 현재 응용프로그램이 실행되고 있는 경로를 알아내 basePath에 할당한다. 현재 응용프로그램의 실행 경로는 AppDomain.CurrentDomain.BaseDirectory 속성을 통해 알아낼 수 있다. 기준 경로가 역 슬래쉬로 끝나지 않을 경우 uri로 변경하게 되면 마지막 경로를 파일명으로 인식하게 된다. “C:\window”라고 지정하면 “C:\”라는 경로로 인식하게 되므로 기준 경로가 역 슬래쉬로 끝나지 않는 경우에는 강제로 역 슬래쉬를 추가해 준다.

다음은 basePathuri로 변경하고 uri MakeRelativeUri 메서드를 호출한다. 이 때 상대 경로로 변경하고자 하는 절대경로를 uri로 만들어 MakeRelativeUri 메서드의 매개변수로 지정한다. 메서드가 호출되면 상대 uri가 반환되며 슬래쉬를 역 슬래쉬로 변경하면 상대 경로가 만들어진다.

다음은 앞에서 만든 메서드를 호출하는 코드이다.

String relativePath = GetRelativePath(“C:\Windows\System32\msxml4.dll”, “c:\Windows”);

 

위와 같이 호출하면 “c:\Window” 경로를 기준으로 msxml4.dll의 상대 경로인 “System32\msxml4.dll”을 알아낼 수 있다.

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET' 카테고리의 다른 글

상대경로 알아내기  (0) 2011/01/03
비동기 프로그램 작성하기 - 2  (0) 2010/11/15
비동기 프로그램 작성하기 - 1  (0) 2010/11/15
Enum Type의 버그?  (2) 2010/01/18
.NET Framework 라이브러리 소스 코드 공개  (0) 2008/01/17
Posted by kyeongkyun(kobukii) kyeongkyun

Task
패턴

새로운 형식인 System.Threading.Tasks.Task 비동기 연산을 다른 개발 방법으로 .NET Framework 4에서 도입되었다. Task CPU상에서 실행되는 일반적인 연산으로 있다.

static void Main() {

  Task<double> task = Task.Factory.StartNew(() => {

    double result = 0;

    for (int i = 0; i < 10000000; i++)

      result += Math.Sqrt(i);

    return result;

  });

 

  Console.WriteLine("The task is running asynchronously...");

  task.Wait();

  Console.WriteLine("The task computed: {0}", task.Result);

}

 

Task 기본적으로 스레드 풀에서 코드를 실행하는 작업에 대응하는 StartNew 메서드를 사용하여 생성된다. StartNew 메서드를 사용하여 생성된 Task 스레드 풀에서 동작하는 코드를 실행하는 작업에 해당한다. 하지만, Task 보다 일반적인 비동기 연산을 표현할 있다. 말하자면, 서버와 통신하거나 디스크에서 데이터를 읽는 것이 이에 해당한다.

TaskCompletionSource 비동기 연산을 나타내는 Task 생성하는 일반적인 매카니즘이다. TaskCompletionSource 정확히 하나의 작업에만 연결된다. TaskCompletionSource 상에서 SetResult 메서드가 한번 호출되면, 연관된 Task 완료하고 Task 결과를 반환한다(그림 4 참조).

그림 4 TaskCompletionSource 사용하기

static void Main() {

// TaskCompletionSource를 생성하고 연관된 Task를 얻어낸다.

  TaskCompletionSource<int> tcs =

    new TaskCompletionSource<int>();

  Task<int> task = tcs.Task;

 

  // 비동기적으로 TaskCompletionSourceSetResult를 호출한다.

  ThreadPool.QueueUserWorkItem( _ => {

    Thread.Sleep(1000); // Do something

    tcs.SetResult(123);

  });

 

  Console.WriteLine(

    "The operation is executing asynchronously...");

  task.Wait();

 

// 그리고 task 변수에 있는 result를 얻어낸다.

  Console.WriteLine("The task computed: {0}", task.Result);

}

 

여기서 필자는 TaskCompletionSource 있는 SetResult 호출하기 위해 스레드 스레드를 사용한다. 하지만, 가장 주목해야 점은 SetResult 메서드가 TaskCompletionSource 접근할 있는 모든 코드에서 호출될 있다는 것이다. Button.Click 이벤트를 위한 이벤트 처리기, 개의 연산이 완료된 Task 그리고 요청에 대한 서버의 응답 때문에 발생되는 이벤트와 같은 곳에서 호출 있다.

, TaskCompletionSource 비동기 작업을 구현하기 위한 매우 일반적인 메커니즘이라 있다.

IAsyncResult 패턴 변환

비동기 프로그래밍 위해 Task 사용할 때는 이전 방식들을 사용한 비동기 연산과 상호작용 하는 것이 중요하다. TaskCompletionSource 모든 비동기 작업을 감싸 Task 노출할  있는 반면, Task API FromAsync 메서드를 통해 IAsyncResult 패턴을 Task 변환하는 편리한 메커니즘을 제공한다.

예제는 IAsyncResult 기반의 비동기 연산인 Dns.BeginGtHostAddress Task 변환하기 위해 FromAsync 메서드를 사용한 것이다.                  

static void Main() {

  Task<IPAddress[]> task =

    Task<IPAddress[]>.Factory.FromAsync(

      Dns.BeginGetHostAddresses,

      Dns.EndGetHostAddresses,

      "http://www.microsoft.com", null);

  ...

}

 

FromAsync 메서드는 IAsyncResult 비동기 연산을 Task 쉽게 변환할 있도록 해준다. 내부적으로, FromAsync 메서드는 스레드 풀을 활용하는 TaskCompletionSource 예제와 비슷한 방법으로 구현된다. 다음 코드는 GetHostAddress 직접 대상으로 하는 경우이다.

static Task<IPAddress[]> GetHostAddressesAsTask(

  string hostNameOrAddress) {

 

  var tcs = new TaskCompletionSource<IPAddress[]>();

  Dns.BeginGetHostAddresses(hostNameOrAddress, iar => {

    try {

      tcs.SetResult(Dns.EndGetHostAddresses(iar)); }

    catch(Exception exc) { tcs.SetException(exc); }

  }, null);

  return tcs.Task;

}

 

이벤트 기반 패턴으로 변환

TaskCompletionSource 클래스를 사용하여 이벤트 기반 비동기 작업을 Task 변환할 있다. Task 클래스는 변환을 도와주는 내장된 메커니즘을 제공하지 않는다. 이벤트 기반 비동기 패턴은 그냥 규칙에 불과하기 때문에 일반적인 메커니즘은 사용할 없다.

이벤트 기반 비동기 작업을 task 변환하는 방법은 다음과 같다. 다음 코드는 Uri 매개변수로 받고 비동기 작업인 WebClient.DownloadStringAsync 가리키는Task 반환하는 방법을 보여준다.

static Task<string> DownloadStringAsTask(Uri address) {

  TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

  WebClient client = new WebClient();

  client.DownloadStringCompleted += (sender, args) => {

             if (args.Error != null) tcs.SetException(args.Error);

             else if (args.Cancelled) tcs.SetCanceled();

             else tcs.SetResult(args.Result);

  };

  client.DownloadStringAsync(address);

  return tcs.Task;

}

 

패턴과 이전 섹션의 패턴을 사용하여, 존재하는 모든 비동기 패턴(이벤트 기반 혹은 IAsyncResult 기반) Task 변환할 있다.

Task 조작 구성

그래서, 비동기 작업을 표현하기 위해 Task 사용하는 것일까? Task 비동기 작업을 편리하게 조작하고 구성할 있는 방법을 제공하는 것이 가장 중요한 이유다. IAsyncResult 이벤트 기반의 방식과는 다르게, Task 비동기 연산에 참여하는 방법, 결과를 얻는 방법등과 같은 비동기 작업에 관련된 모든 정보를 관리하는 단일 개체를 제공한다.

Task 통해 있는 한가지 유용한 것은 Task 완료될 때까지 기다리는 것이다. 여러분은 하나의 Task 기다리거나 모든 Task들이 완료로 설정될 때까지 기다리고 특정 Task 완료로 설정될 때까지 기다릴 있다.

static void Main() {

  Task<int> task1 = new Task<int>(() => ComputeSomething(0));

  Task<int> task2 = new Task<int>(() => ComputeSomething(1));

  Task<int> task3 = new Task<int>(() => ComputeSomething(2));

 

  task1.Wait();

  Console.WriteLine("Task 1 is definitely done.");

 

  Task.WaitAny(task2, task3);

  Console.WriteLine("Task 2 or task 3 is also done.");

 

  Task.WaitAll(task1, task2, task3);

  Console.WriteLine("All tasks are done.");

}

 

Task 다른 유용한 기능은 Task 완료 되자마자 계속해서 실행될 Task 예약할 있는 능력이다. 작업이 완료되기를 기다리는 것과 유사하게 특정 Task 완료되거나 모든 Task 완료되었을 , 혹은 개의 Task 완료된 다음의 동작을 계속해서 예약할 있다.

예제는 www.microsoft.com에서 DNS 쿼리하는 Task 생성한다. Task 완료되면, 이어지는 Task 시작되고 결과를 콘솔에 표시할 것이다.

static void Main() {

  Task<IPAddress[]> task =

    Task<IPAddress[]>.Factory.FromAsync(

      Dns.BeginGetHostAddresses,

      Dns.EndGetHostAddresses,

      "www.microsoft.com", null);

 

  task.ContinueWith(t => Console.WriteLine(t.Result));

  Console.ReadKey();

}

 

비동기 작업을 표현하는 Task 강력함을 보여주는 보다 흥미로운 예제를 보도록 하자. 그림 5 병렬로 개의 DNS 조회하는 예제다. 비동기 작업이 Task 표현될 , 쉽게 여러 개의 작업이 완료될 때까지 기다릴 있다.

그림 5 병렬로 작업 실행하기

static void Main() {

  string[] urls = new[] { "www.microsoft.com", "www.msdn.com" };

  Task<IPAddress[]>[] tasks = new Task<IPAddress[]>[urls.Length];

 

  for(int i=0; i<urls.Length; i++) {

    tasks[i] = Task<IPAddress[]>.Factory.FromAsync(

      Dns.BeginGetHostAddresses,

      Dns.EndGetHostAddresses,

      urls[i], null);

  }

 

  Task.WaitAll(tasks);

 

  Console.WriteLine(

    "microsoft.com resolves to {0} IP addresses. msdn.com resolves to {1}",

    tasks[0].Result.Length,

    tasks[1].Result.Length);

}

 

다음 가지 단계의 작업을 수행하는 Task 구성하는 예제를 보도록 하자.

1.    1. 여러 개의 HTML 페이지들을 병렬로 비동기 다운로드한다.

2.    2. HTML 페이지를 처리한다.

3.    3. HTML 페이지에서 정보를 수집한다.

그림 6 앞에서 보여준 DownloadAsyncAsTask 메서드의 사용하여 이련 조작이 어떻게 구현될 있는지를 보여준다. 구현방법에서 한가지 주목해야 점은 개의 서로 다른 CountParagraphs 메서드가 서로 다른 스레드에서 실행된다는 것이다. 최근 멀티 코어 장비들이 보급됨에 따라, 복잡한 작업을 여러 개의 스레드에 분산시키는 프로그램이 성능 향상을 가져오게 된다.

그림 6 비동기적으로 문자열 다운로드하기

static void Main() {

  Task<string> page1Task = DownloadStringAsTask(

    new Uri("http://www.microsoft.com"));

  Task<string> page2Task = DownloadStringAsTask(

    new Uri("http://www.msdn.com"));

 

  Task<int> count1Task =

    page1Task.ContinueWith(t => CountParagraphs(t.Result));

  Task<int> count2Task =

    page2Task.ContinueWith(t => CountParagraphs(t.Result));

 

  Task.Factory.ContinueWhenAll(

    new[] { count1Task, count2Task },

    tasks => {

      Console.WriteLine(

        "<P> tags on microsoft.com: {0}",

        count1Task.Result);

      Console.WriteLine(

        "<P> tags on msdn.com: {0}",

        count2Task.Result);

  });

   

  Console.ReadKey();

}

 

동기화 컨텍스트에서 Task 수행하기

때로는, 특정 동기화 컨텍스트 상에서 실행되는 Task 연속으로 예약 가능할 있는 기능은 상당히 유용하다. 예를 들어, UI 있는 응용프로그램에서, UI 스레드 상에서 수행될 Task 연속으로 예약될 있다면 이는 상당히 유용할 것이다.

동기화 컨텍스트에서 Task 얻는 가장 쉬운 방법은 현재 스레드의 컨텍스트를 캡쳐하는 TaskScheduler 생성하는 것이다. UI 스레드를 위한 TaskScheduler 얻기 위해서는 UI 스레드에서 동작하는 동안 TaskSchedluer 타입의 정적 메서드인 FromCurrentSynchronizationContext 호출해야 한다.

다음은 www.microsoft.com 웹페이지를 비동기적으로 다운로드하고 다운로드된 HTML WPF 텍스트 박스의 Text 속성에 할당하는 예제이다.

void Button_Click(object sender, RoutedEventArgs e) {

  TaskScheduler uiTaskScheduler =

    TaskScheduler.FromCurrentSynchronizationContext()

 

  DownloadStringAsTask(new Uri("http://www.microsoft.com"))

    .ContinueWith(

       t => { textBox1.Text = t.Result; },

       uiTaskScheduler);

}

 

Button_Click 메서드의 내용은 마지막에 UI 업데이트하는 비동기 작업이 구현되어 있지만, Button_Click 메서드는 연산이 완료될 때까지 기다리지 않는다. 이와 같은 방식을 통해, UI 스레드는 블록 되지 않을 것이며, 계속해서 UI 업데이트하고 사용자의 액션에 응답할 있게 된다.

이전에 말했듯이, .NET 프레임워크 4 이전의 비동기 작업은 IAsyncResult 패턴이나 이벤트 기반 패턴을 사용했었다. .NET 프레임워크 4 에서는 비동기 작업의 다른 유용한 방식으로 Task 클래스를 사용할 있게 되었다. 비동기 작업을 Task 표현하면 비동기 작업을 구성하고 처리하기 더욱 쉬워진다. Task 통해 비동기 프로그래밍을 처리하는 여러 가지 예제가 ParallelExtensionsExtras 샘플에 포함되어 있고 code.msdn.microsoft.com/ParExtSamples 에서 다운로드 있다.

오역이 있을 수도 있으니 문제가 있는 부분은 메일이나 코멘트로 알려주세요^^

원문 : http://msdn.microsoft.com/en-us/magazine/ff959203.aspx

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET' 카테고리의 다른 글

상대경로 알아내기  (0) 2011/01/03
비동기 프로그램 작성하기 - 2  (0) 2010/11/15
비동기 프로그램 작성하기 - 1  (0) 2010/11/15
Enum Type의 버그?  (2) 2010/01/18
.NET Framework 라이브러리 소스 코드 공개  (0) 2008/01/17
Posted by kyeongkyun(kobukii) kyeongkyun

비동기 프로그래밍은 복잡한 작업이 프로그램의 나머지 부분들과 동시에 실행될 있도록 구현하는 기술이다. 비동기 프로그래밍은 주로 복잡한 작업이 실행될 UI 동작하지 않고 멈추어 버리는 현상이 나타나면 되는 그래픽 UI 있는 프로그램에서 사용된다. 또한, 비동기 작업은 동시에 여러 클라이언트의 요청을 처리 필요가 있는 서버 응용프로그램에서도 상당히 중요하다. 서버로 요청을 보내고 응답을 기다리거나 하드디스크에서 데이터를 읽고 맞춤법 검사와 같은 복잡한 연산을 수행하는 작업들이 비동기 연산의 대표적이 예들이다UI 갖는 응용프로그램을 예로 들어보자. 응용프로그램은 WPF Windows Form으로 만들어 있다. 이와 같은 응용프로그램에서 여러분이 작성한 대부분의 코드는 UI 컨트롤에서 발생하는 이벤트에 대한 처리기를 수행하기 때문에 UI 스레드에서 실행된다. 사용자가 버튼을 클릭하면, UI 스레드는 메시지를 받게 것이고, 여러분이 작성한 Click 이벤트 처리기를 수행하게 된다.

다음의 Click 이벤트 처리기는 응용프로그램이 서버로 요청을 보내고 응답을 기다리는 동작을 수행한다.

// !!! 문제가 있는 코드!!!

void Button_Click(object sender, RoutedEventArgs e) {

  WebClient client = new WebClient();

  client.DownloadFile("http://www.microsoft.com", "index.html");

}

 

코드에는 심각한 문제가 있다. 웹사이트를 다운로드데 이상의 시간이 소요된다. 결국, Button_Click 호출하게 되면 반환되기까지 이상이 걸릴 것이다. 이것은 UI 스레드가 초간 블록되고 UI 멈추어 버리게 된다는 것을 의미한다. 멈춰버린 인터페이스는 사용자 경험을 불편하게 만들며 사용자의 마음에 들지 않을 것이다.

서버에서 응답이 오기 이전일지라도 응용프로그램 UI 반응하도록 만들기 위해서는 UI 스레드에서 동기적으로 다운로드 되지 않도록 만드는 것이 중요하다.

UI 멈춰버리는 문제를 수정해 보자. 차선책이긴 하지만 UI 스레드가 멈춰 버리지 않도록 다른 스레드에서 통신하게 되면 이러한 문제를 해결 있다.

//문제를 해결한 차선 코드

void Button_Click(object sender, RoutedEventArgs e) {

  ThreadPool.QueueUserWorkItem(_ => {

    WebClient client = new WebClient();

    client.DownloadFile(

      "http://www.microsoft.com", "index.html");

  });

}

 

코드 샘플은 번째 예제의 문제를 해결해 준다. 이제 Button_Click 이벤트는 UI 스레드를 블록하지는 않는다. 하지만 스레드 기반의 해결방법은 가지의 중요한 문제를 가지고 있다. 문제들에 대해 자세히 알아보도록 하자.

문제1: 낭비되는 스레드 스레드

위의 수정된 코드는 서버에 요청을 전달하고 응답이 때까지 기다리기 위해 스레드 풀의 스레드를 사용한다.

스레드 스레드는 서버가 응답할 때까지 아무런 일도 하지 않으며 블록될 것이다. 스레드는 WebClient.DownloadFile 호출이 완료될 까지 풀에 반환될 수도 없다. UI 멈춰 버리지는 않기 때문에 스레드 스레드가 블록되는 것이UI 스레드가 블록되는 보다는 훨씬 낫다. 하지만 스레드 풀의 스레드 하나를 낭비하게 된다.

가끔 여러분의 응용프로그램이 스레드 스레드를 잠시 동안만 블록한다면, 성능상의 불이익은 무실할 수도 있다. 그러나 빈번하게 블록된다면, 응용프로그램의 반응은 스레드 풀에 부하를 주기 때문에 응답이 느려질 것이다. 스레드 풀은 많은 스레드를 생성하여 대처하게 되며, 이는 현저한 성능 저하를 가져오게 것이다.

아티클에서 설명하는 비동기 프로그래밍 패턴들은 모두 스레드 스레드가 낭비되는 문제를 해결한 패턴들이다.

문제 2 : 결과 반환하기

스레드를 사용하여 비동기 프로그래밍을 하는 데는 다른 어려움이 있다.

가장 처음의 예제에서, DownloadFile 메서드는 다운로드된 웹페이지를 로컬 파일에 기록하며 void 반환한다. 다운로드된 웹페이지를 파일로 작성하는 대신, 전달 받은 HTML TextBox(이름이 HtmlTextbox) Text 속성에 할당하는 경우와 같은 다른 상황을 고려해 보자.

이를 구현하는 기본적인 방식은 다음과 같다. 하지만 코드는 잘못된 것이다.

//잘못된 코드

void Button_Click(object sender, RoutedEventArgs e) {

  ThreadPool.QueueUserWorkItem(_ => {

    WebClient client = new WebClient();

    string html = client.DownloadString(

      "http://www.microsoft.com", "index.html");

    HtmlTextBox.Text = html;

  });

}

 

문제는 UI 컨트롤(HtmlTextBox) 스레드 스레드로부터 수정되는 것이다. 오직 UI 스레드만이 UI 수정할 있기 때문에 코드는 잘못된 것이다. 이러한 이유로 WPF Windows Form에서 이런 문제들이 발생하게 된다.

문제를 해결하려면, UI 스레드 상에서 동기화 컨텍스트를 알아낸 다음, 스레드 스레드에서 컨텍스트로 메시지를 전송해야 한다.

void Button_Click(object sender, RoutedEventArgs e) {

  SynchronizationContext ctx = SynchronizationContext.Current;

  ThreadPool.QueueUserWorkItem(_ => {

    WebClient client = new WebClient();

    string html = client.DownloadString(

      "http://www.microsoft.com");

    ctx.Post(state => {

      HtmlTextBox.Text = (string)state;

    }, html);

  });

}

 

핼퍼 스레드에서 값을 반환하는 문제는 UI 있는 응용프로그램에만 해당하는 것이 아니다. 일반적으로, 하나의 스레드에서 다른 스레드로 값을 반환하는 것은 동기화 원리에 대한 지식을 요구하는 까다로운 문제이다.

 

문제 3 : 비동기 연산 구성하기

명시적으로 스레드를 사용할 경우 비동기 연산을 구성하기 어렵다. 예를 들면, 병렬로 여러 페이지를 다운로드 하는 경우, 동기화 코드를 작성하기가 어렵고 오류가 발생하기 쉽다.

이와 같은 방식의 구현은 실행중인 비동기 연산의 카운터를 관리할 것이다. 카운터는 Interlocked.Decrement 사용하여 스레드에 안전한 방식으로 수정되어야 하고, 카운터가 zero 도달하게 되면, 다운로드를 처리하는 코드가 실행될 것이다. 이로 인해 코드의 양이 많아지며, 오류가 발생하기 쉬워 것이다.

말할 필요도 없이, 더욱 복잡하게 구성된 패턴일 수록, 스레드 기반의 패턴을 적용하여 정확하게 구현하기가 더욱 어려워 것이다.

이벤트 기반 패턴

Microsoft .NET 프레임워크의 비동기 프로그래밍에서 자주 사용되는 패턴 한가지가 이벤트 기반 모델이다. 이벤트 모델은 비동기 연산을 시작하기 위한 메서드를 노출하고 연산이 완료되면 이벤트를 발생시킨다.

이벤트 패턴은 비동기 연산을 노출하기에는 편리하지만, 인터페이스를 통한 통신처럼 명확한 규정이 없다. 얼마나 충실하게 패턴을 따를지는 클래스를 구현하는 사람이 결정하는 것이다. 그림 1 이벤트 기반의 비동기 프로그래밍 패턴을 제대로 구현하여 노출한 메서드들이다.

그림 1 이벤트 기반 패턴의 메서드

public class AsyncExample {

  // 동기 메서드

  public int Method1(string param);

  public void Method2(double param);

 

  // 비동기 메서드

  public void Method1Async(string param);

  public void Method1Async(string param, object userState);

  public event Method1CompletedEventHandler Method1Completed;

 

  public void Method2Async(double param);

  public void Method2Async(double param, object userState);

  public event Method2CompletedEventHandler Method2Completed;

 

  public void CancelAsync(object userState);

 

  public bool IsBusy { get; }

 

  // Class implementation not shown.

  ...

}

 

WebClient클래스는 이벤트 기반의 패턴으로 비동기 연산을 구현하는 .NET 프레임워크에서 제공하는 클래스이다. WebClient 클래스는DownloadString 메서드에 대한 비동기 작업을 처리하기 위해 DownloadStringAsync메서드와  CancelAsync메서드  그리고 DownloadStringCompleted 이벤트를 제공한다. 다음은 DownloadString 비동기적으로 구현하는 방법을 보여주는 예제이다.

void Button_Click(object sender, RoutedEventArgs e) {

  WebClient client = new WebClient();

  client.DownloadStringCompleted += eventArgs => {

      HtmlTextBox.Text = eventArgs.Result;

  };

  client.DownloadStringAsync("http://www.microsoft.com");

}

 

위의 코드는 문제 1에서 말한 불필요하게 스레드를 블록킹하는 효과적이지 못한 스레드 기반의 방식을 해결한다. DownloadStringAsync 호출하면 즉시 반환되고 UI스레드나 스레드 스레드가 블록되지 않는다. 백그라운드에서 다운로드가 실행되고 다운로드가 완료되면 DownloadStringCompleted 이벤트가 해당 스레드(UI 스레드)에서 실행된다.

스레드 기반의 솔루션에서 필요했던 SynchronizationContext 코드 없이 DownloadStringCompleted 이벤트 처리기가 해당 스레드에서 실행된다는 것을 주목해라. WebClient 내부적으로 SynchronizationContext 캡쳐한 다음 컨텍스트로 콜백을 전송한다. 이벤트 기반 패턴을 구현하는 클래스는 보통 해당 스레드에서 Completed 처리기를 수행하게 것이다.

이벤트 기반의 비동기 프로그래밍 패턴은 불필요하게 스레드를 블록하지 않는다는 관점에서 효과적이며, .NET 프레임워크 전반에 거쳐 널리 사용되는 가지 패턴 하나이다. 그러나 이벤트 기반 패턴은 가지 제약이 있다.

l  패턴은 비공식적이며 표기법에 불과하다. 클래스는 패턴을 지키지 않을 수도 있다.

l  병렬로 실행되는 비동기 연산을 처리하거나 순차적으로 비동기 연산을 처리하는 것처럼 다양한 비동기 연산을 구성하기에는 상당히 까다로울 있다.

l  여러분은 비동기 연산이 완료 되었는지를 알아내거나 검사할 없다.

l  이러한 형식을 이용할 때는 세심한 주의가 필요하다. 예를 들어, 하나의 인스턴스가 여러 개의 비동기 연산을 처리하는데 사용된다면, 등록된 이벤트 처리기가 여러 호출 될지라도, 반드시 대상이 되는 오직 하나의 비동기 연산만을 처리하도록 작성되어야 한다.

l  이벤트 처리기는 항상 비동기 연산이 수행될 캡쳐한 SynchronizationContext 상에서 호출될 것이다. UI 스레드가 필요하지 않은 경우에도 마찬가지이므로, 추가적인 성능 비용이 발생한다.

l  구현하기 어려울 있으며, 이벤트 처리기나 이벤트 매개변수 같은 여러 개의 타입을 정의해야 한다.

그림 2 이벤트 기반 비동기 패턴을 구현하는 .NET Framework 4에서 제공하는 클래스 목록이다.

그림 2 .NET클래스에서 이벤트 기반 비동기 패턴의

Class

Operation

System.Activities.WorkflowInvoker

InvokeAsync

System.ComponentModel.BackgroundWorker

RunWorkerAsync

System.Net.Mail.SmtpClient

SendAsync

System.Net.NetworkInformation.Ping

SendAsync

System.Net.WebClient

DownloadStringAsync

 

IAsyncResult 패턴

.NET에서 비동기 연산을 구현하는 다른 방법은 IAsyncResult 패턴을 사용하는 것이다. 이벤트 기반 모델과 비교할 경우, IAsyncResult 더욱 향상된 방식의 비동기 프로그래밍 솔루션이다.

IAsyncResult 패턴에서, 비동기 연산은 Begin End 메서드를 사용한다. 여러분은 비동기 연산을 초기화 시작하기 위해 Begin 메서드를 호출하고, 연산이 완료되었을 호출하게 델리게이트를 전달한다. 그리고 콜백에서 비동기 연산의 결과를 반환하는 End 메서드를 호출한다. 콜백을 제공하는 대신, 연산이 완료되었는지를 폴링하거나 동기적으로 완료되기를 기다릴 있다.

호스트 이름을 받고 IP 주소들을 반환하는 Dns.GetHostAddress 메서드의 예를 보도록 하자. 동기적으로 수행되는 메서드의 시그니처는 다음과 같다.

public static IPAddress[] GetHostAddresses(

  string hostNameOrAddress)

 

비동기 버전은 다음과 같다.

public static IAsyncResult BeginGetHostAddresses(

  string hostNameOrAddress,

  AsyncCallback requestCallback,

  Object state)

 

public static IPAddress[] EndGetHostAddresses(

  IAsyncResult asyncResult)

 

다음은 www.microsoft.com 대한 DNS 비동기적으로 조회하는 BeginGetHostAddress EndGetHostAddress 메서드를 사용하는 예제다.

static void Main() {

  Dns.BeginGetHostAddresses(

    "www.microsoft.com",

    result => {

      IPAddress[] addresses = Dns.EndGetHostAddresses(result);

      Console.WriteLine(addresses[0]);

    },

    null);

  Console.ReadKey();

}

 

그림 3 이벤트 기반 패턴을 사용하여 비동기 연산을 구현하는 .NET 클래스 가지 이다. 그림 2 그림 3 비교해보면, 어떤 클래스는 이벤트 기반 패턴을 구현하고 어떤 클래스는 IAsyncResult 패턴을 구현하며, 또한 어떤 클래스는 패턴을 모두 구현하는 것을 있을 것이다.

그림 3 .NET 클래스의 IAsyncResult 예제

Class

Operation

System.Action

BeginInvoke

System.IO.Stream

BeginRead

System.Net.Dns

BeginGetHostAddresses

System.Net.HttpWebRequest

BeginGetResponse

System.Net.Sockets.Socket

BeginSend

System.Text.RegularExpressions.MatchEvaluator

BeginInvoke

System.Data.SqlClient.SqlCommand

BeginExecuteReader

System.Web.DefaultHttpHandler

BeginProcessRequest

 

역사적으로 , IAsyncResult 패턴은 비동기 API 구현하기 위한 성능의 방식으로 .NET 프레임워크 1.0에서 공개 되었다. 그러나 IAsyncResult 패턴은 UI 스레드와 상호작용을 위해 추가적인 작업을 요구하며, 정확하게 구현하기 어렵고 사용하기도 어려울 있다. 이벤트 기반 패턴은 IAsyncResult 의해 처리하기 어려운 UI 쪽을 쉽게 처리하기 위해 .NET 프레임워크 2.0에서 공개되었으며 이벤트 기반 패턴은 UI 응용프로그램이 단일 비동기 응용프로그램을 실행한 다음 어떤 작업을 수행하는 시나리오에 초점이 맞추어져 있다.

 

다음 포스트는 Task를 사용하여 비동기 프로그램을 작성하는 방법입니다.

원문 : http://msdn.microsoft.com/en-us/magazine/ff959203.aspx

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET' 카테고리의 다른 글

상대경로 알아내기  (0) 2011/01/03
비동기 프로그램 작성하기 - 2  (0) 2010/11/15
비동기 프로그램 작성하기 - 1  (0) 2010/11/15
Enum Type의 버그?  (2) 2010/01/18
.NET Framework 라이브러리 소스 코드 공개  (0) 2008/01/17
Posted by kyeongkyun(kobukii) kyeongkyun

역자로써 직접 번역에 참여한 jQuery 쿡북이 드디어 출간되네요. 기쁘기도 하고 설레기도 하지만 좋은 원서를 부족한 번역 실력 때문에 망치지는 않았나 하는 걱정이 앞서네요. 아무쪼록 많이들 보시고 개발하는데 많은 도움이 되었으면 좋겠습니다.

다음은 Taeyo.net에 올라온 예약 판매 관련 내용입니다.


안녕하십니까? 태오입니다.

일전에 말씀드렸던 jQuery CookBook 서적의 번역이 완료되고, 출간을 앞두고 있습니다
공식 출간일은 6월 24일이며, 모든 인터넷 서점에서 구매가 가능합니다.

아마존에서 워낙 극찬을 받은 서적이라, 나름 부담감을 가지고 편역작업을 진행했어요.
원서는 jQuery 1.3 기준으로 작성되어 있지만, 번역 및 편역을 하면서 1.4.2 (최신버전)을 반영하였습니다.

예약 공동구매 이벤트는 비제이퍼플릭(출판사)와 인터파크와 함께 진행을 하기로 했습니다.
책가격이 3만원이 넘지만, 공구 이벤트를 통하시면 2만원 선에서 구매가 가능하다기에 공구를 진행하기로 했습니다. ^^

즉, 원가는 32,000원이지만, 예약 공동구매를 하게 되면
기본할인 10%(-3,200원), 적립10%(2,880P)에 추가적으로 추가쿠폰 15%(4,800원)을 적용할 수 있게 되기에
결제는 24,000원에 하시고, 적립금 2,880P 받으시게 되어 결과적으로 21,120원에 구매할 수 있다고 합니다.

단, 이는 6월 25일까지만 한정적으로 진행되는 이벤트입니다.

구매방법은 다음과 같습니다.

---------------------------------

1. 인터파크 도서 회원은 아래의 링크에 들어가서, 
   인터파크 도서 계정으로 로그인을 하면 할인쿠폰이 발급됩니다.
 
- "실전 JQUERY 쿡북" 쿠폰 발급 링크
   http://book.interpark.com/coupon/CouponFntMgt.do?_method=popup&sc.couponNo=210442
 
- 위 링크를 클릭하고 로그인하면 쿠폰은 자동 발급됩니다. ( 4,800원 할인쿠폰)
- 이 쿠폰은 각각 해당 도서 구매시에만 사용가능하며, 사용기간은 6월 25일까지 입니다.
 
2. 인터파크 도서 검색창에서 각각 "실전 JQUERY 쿡북" 검색 또는 아래 링크를 클릭하시고, 
  "결재과정"을 진행하면 쿠폰을 사용할 수 있습니다.
 
- "실전 JQUERY 쿡북" 도서 구매 링크
http://book.interpark.com/product/BookDisplay.do?_method=Detail&sc.shopNo=0000400000&dispNo=&sc.prdNo=204892367

---------------------------------

부디 마구마구 널리 퍼 날라 주시기 바랍니다~~~  
이 책이 필요하신 분에게는 저렴하게 구매할 수 있는 기회이니까요. (그쵸? 그쵸?)

다음은 태오가 추천하는 jQuery 서적, 실전 jQuery CookBook의 서적 정보입니다.

*제목: 실전 jQuery 쿡북 (Taeyo's Choice - v1.4.x 증보판)
*저자: jQuery 코어 커뮤니티
*역자: 김경균, 최지훈
*편역/감수: 김태영

*출판사: 비제이퍼블릭
*출간일: 2010년 6월 24일
*정가: 32,000원
*페이지: 610p
*판형: 175 x 230
*ISBN: 978-89-962765-5-5(13560)
*원서정보: jQuery Cookbook (O’Reilly 출판사)

---------------------------------
아마존닷컴의 [웹 개발 > 프로그래밍 언어] 분야 베스트셀러!

19인의 jQuery 코어 개발자로부터 배우는 jQuery 패턴과 실용 레시피 (v1.4.x 증보판)

jQuery는 풍부하고 상호작용이 가능한 웹 프론트엔드를 상당히 쉽게 개발할 수 있게 해 준다. 
jQuery 라이브러리를 사용하기 시작하는 것은 간단한 반면, 그 진가를 폭넓게 이해하는 데에는 
수 개월 또는 수 년이 걸릴 수 있다. 『실전 jQuery쿡북』은 상당히 짧은 학습곡선을 가지고 있다. 
이 책에서 제공하는 레시피들을 통해 여러분은 단순한 컴포넌트를 웹사이트와 응용 프로그램에 통합하는 것부터 
고성능의 복합 사용자 인터페이스를 개발하는 것에 이르기까지의 모든 것에 대해 jQuery를 사용하는 18명의 
주요 개발자로부터 패턴과 실습을 배우게 될 것이다.

jQuery 초보자 및 자바스크립트 전문가들 모두에게 적합하도록 『실전 jQuery 쿡북』은 
기본적인 문제로부터 시작하여 실제 사용 사례로 나아가는 방식을 취하고 있으며, 
일반적인 웹 개발 문제들을 풀어내기 위해서 검증된 해결 방법을 사용하고 있다. 
또한 대규모 프로젝트에 jQuery를 적용하는 방법과 같은 고급 주제에 관한 레시피도 볼 수 있을 것이다.

- 이벤트, 효과, 차원(dimension), 폼, 테마 그리고 사용자 인터페이스 요소와 관련된 문제를 해결한다.
- 폼을 향상시키기 위한 방법과 페이지에 요소를 배치하거나 재배치하는 방법을 배운다.
- 사용자 정의 이벤트와 사용자 정의 이벤트 데이터를 포함하는 jQuery의 이벤트 관리 시스템 대부분을 만든다.
- 탭, 아코디언, 모달 같은 UI 요소를 처음부터 직접 만들어 본다.
- 병목현상을 제거하고 최고의 성능을 보장하기 위해 코드를 최적화한다.
- jQuery 응용프로그램을 테스트하는 방법에 대해 배운다.
- jQuery UI CSS 프레임워크와 테마를 적용한다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

jQuery CDN 성능

Javascript 2010/05/12 12:32

pingdom.com이라는 사이트에서 jQuery CDN관련한 성능 테스트를 수행했는데 jQuery CDN > MS CDN >
Google CDN 순으로 성능이 좋다는 결과가 나왔다네요.

유럽에서는 MS CDN의 성능이 가장 좋으며 북미와 모든 지역을 대상으로한 테스트에서는 jQuery CDN이 가장 높은
성능을 내고 있다 합니다. 아시아에서 테스트한 결과는 없지만 jQuery CDN을 제공하고 있는 EdgeCast가 도쿄에 네
트워크를 가지고 있는 것으로 보아 아시아에서도 jQuery CDN의 성능이 가장 높지 않을까 하네요.

대부분 로컬 서버에 jQuery를 호스팅하여 사용하고 있겠지만 CDN을 사용한다면, 혹시 Google CDN을 사용하고 있
다면 jQuery 나 MS의 CDN으로 바꿔 보는 것도 좋을 것 같네요.

EdgeCast의 경우 경로를 추적해본 결과 미국의 LA에 있는 Santa Monica로 접속하네요.. 그렇다면 MS CDN이 가장 빠를지도...


jQuery CDN (Edgecast via) : http://code.jquery.com/jquery-1.4.2.min.js
Microsoft CDN : http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js
Google Ajax API CDN : http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js

출처 : http://royal.pingdom.com

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

'Javascript' 카테고리의 다른 글

jQuery CDN 성능  (0) 2010/05/12
컨트롤을 찾기 및 초기화  (0) 2006/10/31
Posted by kyeongkyun(kobukii) kyeongkyun