이 시리즈를 시작하고 참 오래도 걸렸네요 -_-;; 그래도 어떻게든 끝은 봐야겠기에 마지막 포스트를 씁니다! 이번 포스트에서는 간단한 예제를 하나 보여드리려고 합니다. 아주 간단한 예제이므로 굉장히 부주의한 코드가 곳곳에 숨어있으므로 주의 하시기 바랍니다. 허허허허허-_-;;;;


- 그래 무엇을 만들거냥?

보여드릴 예제는 멀티 다운로더 입니다. 다운로드 받을 url을 입력하면 동시에 여러 개의 파일을 다운로드할 수 있게 해주는 프로그램이죠. UI는 WPF로 구성합니다.


- 시이작!

자 그럼 우선, WPF 응용 프로그램을 MultiDownloader라는 이름으로 작성합니다. 작성되고 나면 MainWindow.xaml을 열어서 프로그램의 기본 UI를 다음과 같이 작성합니다.

<Window x:Class="MultiDownloader.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="멀티 다운로드" Height="318" Width="800">
    <Grid Height="284">
        <GroupBox Header="다운로드 목록" Height="238" HorizontalAlignment="Left" 
                  Margin="4,39,0,0" Name="groupBox1" VerticalAlignment="Top" 
                  Width="770">
            <ScrollViewer Height="216" Name="scrollViewer1" Width="754">
                <StackPanel Height="216" Name="downloadStack" Width="738" />
            </ScrollViewer>
        </GroupBox>
        <Button Content="추가" Height="23" HorizontalAlignment="Left" 
                Margin="10,10,0,0" Name="btnAddDownload" VerticalAlignment="Top" 
                Width="75" Click="btnAddDownload_Click" />
    </Grid>
</Window>

아주 간단한 UI인데요, 완성하면 다음과 같은 모양이 됩니다.

이제 '추가'버튼을 눌렀을 때 다운로드할 URL을 입력받을 창을 하나 만들어 보죠. 프로젝트에 AddDownload.xaml이라는 이름으로 창을 하나 추가합니다. 그리고 UI를 다음과 같이 작성합니다.

<Window x:Class="MultiDownloader.AddDownload"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="다운로드 추가" Height="115" Width="581" WindowStartupLocation="CenterOwner">
    <Grid>
        <Label Content="주소 :" Height="28" HorizontalAlignment="Left" 
               Margin="10,10,0,0" Name="label1" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="57,15,0,0" 
                 Name="txtDownloadLink" VerticalAlignment="Top" Width="490" 
                 AcceptsReturn="False" />
        <Button Content="추가" Height="23" HorizontalAlignment="Left" 
                Margin="391,44,0,0" Name="btnOK" VerticalAlignment="Top" 
                Width="75" Click="btnOK_Click" />
        <Button Content="취소" Height="23" HorizontalAlignment="Left" 
                Margin="472,44,0,0" Name="btnCancel" VerticalAlignment="Top" 
                Width="75" Click="btnCancel_Click" />
    </Grid>
</Window>

그러면 다음과 같은 모양이 됩니다.

그리고 AddDownload.xaml.cs에 다음과 같이 코드를 작성합니다.

using System.Windows;
using System.Text.RegularExpressions;
 
namespace MultiDownloader
{
    /// <summary>
    /// AddDownload.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class AddDownload : Window
    {
        //이벤트 형식을 선언한 델리게이트
        public delegate void AddDownloadLink(string url);
        //옵션 삭제 버튼을 처리할 이벤트
        public event AddDownloadLink AddDownloadLinkEvent;
        Regex regex;
 
        public AddDownload()
        {
            InitializeComponent();
 
            //올바른 URL을 검증할 정규식
            regex = new Regex(@"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?");
        }
 
        private void btnOK_Click(object sender, RoutedEventArgs e)
        {
            var result = regex.Match(txtDownloadLink.Text);
 
            //올바른 URL이 아닐경우
            if (!result.Success)
            {
                MessageBox.Show("입력하신 문자열은 올바른 URL이 아닙니닭.");
                return;
            }
 
            //다운로드 상태창을 하나 추가하는 이벤트 호출.
            if (AddDownloadLinkEvent != null)
            {
                AddDownloadLinkEvent(txtDownloadLink.Text);
            }
 
            this.Close();
        }
 
        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }
}

(위 코드의 정규식이 잘 안보이는데요, 옮겨 적자면 @"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?" 입니다.)

정규식을 통해서 올바른 URL이 입력되었는지를 검증하고, 올바른 URL이 입력되었다면 MainWindow로 하여금 입력된 URL에 대한 다운로드 상태를 보여주는 다운로드 상태창을 하나 추가할 것을 이벤트로 알려줍니다. 그러면, 이제 다운로드 상태창을 작성해볼까욤. 프로젝트에 DownloadStatus.xaml이라는 이름으로 사용자 정의 컨트롤을 하나 추가합니다. 그리고 UI를 다음과 같이 구성합니다.

<UserControl x:Class="MultiDownloader.DownloadStatus"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="24" d:DesignWidth="735">
    <Grid Width="735">
        <Label Height="24" HorizontalAlignment="Left" Name="lblFileName"
               VerticalAlignment="Top" Width="154" />
        <ProgressBar Height="24" HorizontalAlignment="Left" Margin="393,1,0,0" 
                     Name="downloadProgress" VerticalAlignment="Top" Width="246" />
        <Button Content="시작" Height="24" HorizontalAlignment="Left" 
                Margin="649,1,0,0" Name="btnOrder" VerticalAlignment="Top" 
                Width="40" Click="btnOrder_Click" />
        <Button Content="삭제" Height="24" HorizontalAlignment="Left" 
                Margin="695,1,0,0" Name="btnDelete" VerticalAlignment="Top" 
                Width="40" Click="btnDelete_Click" />
        <Label Content="진행 :" Height="25" HorizontalAlignment="Left" 
               Margin="160,-1,0,0" Name="label1" VerticalAlignment="Top" />
        <Label Height="25" HorizontalAlignment="Right" Margin="0,0,348,0" 
               Name="lblStatus" VerticalAlignment="Top" Width="188" />
    </Grid>
</UserControl>

그러면 다음과 같은 모양이 됩니다.

그리고 DownloadStatus.xaml.cs에 다음과 같이 코딩합니다.

using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
 
namespace MultiDownloader
{
    /// <summary>
    /// DownloadStatus.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class DownloadStatus : UserControl
    {
        private Task downloadTask;
        private CancellationTokenSource cts;
        private bool isStarted = false;
        private bool isCanceled = false;
        private string url;
        private string fileName;
        WebClient client;
 
        //이벤트 형식을 선언한 델리게이트
        public delegate void DeleteThisStatus(DownloadStatus ds);
        //옵션 삭제 버튼을 처리할 이벤트
        public event DeleteThisStatus DeleteThisStatusEvent;
 
        /// <summary>
        /// 다운로드 상태창 생성자
        /// </summary>
        /// <param name="url">다운로드할 URL</param>
        public DownloadStatus(string url)
        {
            InitializeComponent();
 
            this.url = url;
            string[] temp = url.Split(new char[] {'/'});
            fileName = temp[temp.Length - 1];
            lblFileName.Content = fileName;
        }
 
        /// <summary>
        /// 다운로드 시작/취소 버튼
        /// </summary>
        private void btnOrder_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            ToggleStatus();
 
            if (isStarted)
            {
                try
                {
                    isCanceled = false;
                    StartDownload();
                }
                catch (Exception)
                {
                    MessageBox.Show("다운로드 중 오류가 발생했습니다.");
                    ToggleStatus();
                }
            }
            else
            {
                CancelDownload();
            }
        }
 
        /// <summary>
        /// 다운로드 취소
        /// </summary>
        private void CancelDownload()
        {
            isCanceled = true;            
            cts.Cancel();
            btnDelete.IsEnabled = true;
        }
 
        /// <summary>
        /// 다운로드 시작
        /// </summary>
        private void StartDownload()
        {
            cts = new CancellationTokenSource();
                        
            downloadProgress.Foreground = new SolidColorBrush(
                Colors.Green);
 
            downloadTask = Task.Factory.StartNew(() =>
                {
                    try
                    {
                        //비동기로 다운로드 작업을 시작
                        client = new WebClient();
                        client.DownloadFileAsync(new Uri(url), fileName);
                        client.DownloadFileCompleted += 
                            new AsyncCompletedEventHandler(client_DownloadFileCompleted);
                        client.DownloadProgressChanged += 
                            new DownloadProgressChangedEventHandler(
                                client_DownloadProgressChanged);
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }, cts.Token);
 
            downloadTask.Wait();
        }
 
        //다운로드의 진행도가 바뀔 때마다 호출됨.
        void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            //다운로드가 취소된 경우.
            if (isCanceled)
            {
                isCanceled = !isCanceled;                
                client.CancelAsync();
 
                downloadProgress.Dispatcher.BeginInvoke(new Action(() =>
                {
                    downloadProgress.Foreground = new SolidColorBrush(Colors.Red);
                }));
 
                return;
            }
 
            //다운로드가 계속 진행되는 경우, 진행도를 업데이트
            downloadProgress.Dispatcher.BeginInvoke(new Action(() =>
            {
                downloadProgress.Value = e.ProgressPercentage;
            }));
 
            lblStatus.Dispatcher.BeginInvoke(new Action(() =>
                {
                    lblStatus.Content = string.Format("{0}수신, {1}%",
                        FormatBytes(e.BytesReceived),
                        e.ProgressPercentage);
                }));
        }
 
        //다운로드 완료시 호출
        void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            downloadProgress.Dispatcher.BeginInvoke(new Action(() =>
            {
                downloadProgress.Foreground = new SolidColorBrush(Colors.OrangeRed);
            }));
        }
 
        //버튼의 상태를 관리
        private void ToggleStatus()
        {
            isStarted = !isStarted;
            if (isStarted)
            {
                btnOrder.Content = "취소";
                btnDelete.IsEnabled = false;
            }
            else
            {
                btnOrder.Content = "시작";
                btnDelete.IsEnabled = true;
            }
        }
 
        //다운로드 완료 상태 설정
        private void CompletedStatus()
        {
            btnOrder.IsEnabled = false;
        }
 
        //다운로드 받은 크기를 표시할 메서드
        private static string FormatBytes(long bytes)
        {
            string[] magnitudes =
                new string[] { "GB""MB""KB""Bytes" };
            long max = (long)Math.Pow(1024, magnitudes.Length);
 
            return string.Format("{1:##.##} {0}",
                magnitudes.FirstOrDefault(magnitude => bytes > (max /= 1024)),
                (decimal)bytes / (decimal)max).Trim();
        }
 
        //삭제 버튼 처리기
        private void btnDelete_Click(object sender, RoutedEventArgs e)
        {
            if (DeleteThisStatusEvent != null)
            {
                DeleteThisStatusEvent(this);
            }
        }
    }
}

다운로드를 실행할 Task와 취소 요청을 처리할 CancellationTokenSource, 그리고 URL에 대한 다운로드를 처리할  WebClient객체가 변수로 선언되어 있구요, 추가로 삭제버튼을 누를시에 MainWindow로 하여금 해당 다운로드를 삭제하도록 통지하는 이벤트 역시 정의되어 있습니다. 다운로드를 시작하면 Task객체를 통해서 작업을 할당하고 그에 대한 취소 토큰을 설정해줍니다. 그리고 추가로 다운로드의 상태를 표시할 때 사용할 코드들로 구성이 되어 있습니다. 그러면 마지막으로 MainWindow.xaml.cs로 가서 모든 코드를 엮어 볼까욤.

using System.Collections.Generic;
using System.Windows;
 
namespace MultiDownloader
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        //다운로드 상태창을 저장할 리스트
        private List<DownloadStatus> statusList = new List<DownloadStatus>();
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        //다운로드 링크 추가버튼 처리기
        private void btnAddDownload_Click(object sender, RoutedEventArgs e)
        {
            AddDownload addDownload = new AddDownload();
            addDownload.AddDownloadLinkEvent +=
                    addDownload_AddDownloadLinkEvent;
            addDownload.ShowDialog();
        }
 
        //다운로드 링크 추가성공 처리기
        void addDownload_AddDownloadLinkEvent(string url)
        {
            DownloadStatus downloadStatus = new DownloadStatus(url);
            downloadStatus.DeleteThisStatusEvent += 
                new DownloadStatus.DeleteThisStatus(
                    downloadStatus_DeleteThisStatusEvent);
            statusList.Add(downloadStatus);
            downloadStack.Children.Add(downloadStatus);
        }
 
        //다운로드 상태창 제거 처리기
        void downloadStatus_DeleteThisStatusEvent(DownloadStatus ds)
        {
            downloadStack.Children.Remove(ds);
        }
    }
}

별다른 내용 없이, 그저 이벤트들에 대한 처리기로 구성이 되어 있음을 알 수 있습니다. 다운로드를 추가할 때, AddDownload를 생성하면서 추가버튼이 눌릴 때 발생할 이벤트에 대한 처리와 다운로드 상태 창을 추가하면서 삭제 버튼이 눌릴 때 어떻게 처리할 지에 대한 코드인 것이죠.


- 그럼 실행해보자!

네, 이제 작성이 완료 되었습니다. 그럼 실행을 해봐야죵~? 프로그램을 실행하고 SQL서버 2008 Express에 대한 다운로드 링크를 추가해본 모습입니다.

그리고 시작을 눌러서 진행하면 다음과 같이 각자 다운로드를 시작합니다.

완성된 소스는 맨 아래에 첨부해드리겠습니다~!


- 끗.

네. 뭐 만들고 보니 별 거 없네요. ㅎㅎㅎㅎ 그래도 시리즈를 마무리 지은 거 같아서 마음은 한 결 가볍습니다. 도움이 되신 분이 있으면 좋겠구요, 허접한 시리즈 보시느라 고생하셨습니다. 끗!

저작자 표시 비영리 변경 금지
신고
이번에는 TPL에서 제공하는 옵션에 대해서 알아보겠습니다. 병렬 작업을 위해서는 먼저 병렬로 수행할 수 있게끔 작업을 분할하는 일이 먼저 수행되어야 합니다. 예를 들어서 10000개의 데이터를 4개의 스레드가 나눠서 처리해야 하고, 각 10000개의 작업이 독립적이라면, 이걸 공평하게 4등분 할 것인지, 혹은 다른 방법을 사용할 것인지등을 결정하는 것 말이죠.


- 두 가지 선택.

TPL에서 작업 분할을 할때 사용하는 방법으로 구간 분할과 로드 밸런싱이 있습니다. 구간 분할을 루프의 크기를 스레드의 개수만큼 공평하게 나눠서 처리하는 것을 말합니다. 단순하죠.  :) 그리고 로드밸런싱은
처리할 구간을 미리 정하지 않고, 처리해야 할 작업에 따라서 각 스레드가 처리할 구간의 크기나 스레드의 개수가 유동적으로 변하게 됩니다. 이때 스레드하나가 이 작업을 담당하게 됩니다. 이 스레드는 작업 상황을 지켜보면서 그때 그때 스레드를 생성해서 작업을 맡기등의 역할을 맡게 되는 거죠. 그래서 작업을 끝낸 스레드가 다음에 처리할 구간을 할당받기 전까지 기다리는 시간이 필요하고, 이런 동기화에 따른 부하가 발생하게 되는 거죠. 나눠주는 구간의 크기가 작을 수록 스레드는 자주 구간을 할당받으려고 기다려야 하고 성능은 더 저하되는 거죠. :)

Parallel.For의 기본 동작은 로드 밸런싱을 사용하도록 되어 있습니다. 그래서 위에서 언급한 동기화 이슈가 자주 발생하는 경우 싱글스레드로 실행되는 for루프에 비해서 그다지 성능의 우위를 보이지 못하게 됩니다. 예제로 확인해볼까욤? ㅋㅋ

using System;

using System.Linq;

using System.Threading.Tasks;

using System.Collections.Concurrent;

using System.Collections.Generic;

 

namespace Exam20

{

    class Program

    {

        static void RangeParallelForEach(int[] nums)

        {

            List<int> threadIds = new List<int>();

            var part = Partitioner.Create(0, nums.Length);

 

            Parallel.ForEach(part, (num, state) =>

            {

                if (!threadIds.Contains(Task.CurrentId.Value))

                {

                    threadIds.Add(Task.CurrentId.Value);

                }

 

                for (int i = num.Item1; i < num.Item2; i++)

                {

                    nums[i] = nums[i] * nums[i];

                }

            });

 

            Console.WriteLine("Thread ID list of RangeForEach");

            foreach (var id in threadIds)

            {

                Console.WriteLine("{0}", id.ToString());

            }

        }

 

        static void ParallelFor(int[] nums)

        {

            List<int> threadIds = new List<int>();

 

            Parallel.For(0, nums.Length, (i) =>

            {

                if (!threadIds.Contains(Task.CurrentId.Value))

                {

                    threadIds.Add(Task.CurrentId.Value);

                }

 

                nums[i] = nums[i] * nums[i];

            });

 

            Console.WriteLine("Thread ID list of ParallelFor");

            foreach (var id in threadIds)

            {

                Console.WriteLine("{0}", id.ToString());

            }

        }

 

        static void Main(string[] args)

        {

            int max = 50000000;

            int[] nums1 = Enumerable.Range(1, max).ToArray<int>();

            int[] nums2 = Enumerable.Range(1, max).ToArray<int>();

 

            ParallelFor(nums1);

            RangeParallelForEach(nums2);

        }

    }

}

위 코드를 보면 로드밸런싱을 사용하는 Parallel.For와 Partitioner.Create 메서드를 이용해서 구간분할을 사용하도록 설정한 Parallel.ForEach를 사용하는 코드를 포함하고 있습니다. 이렇게 수행하면서 각 방법 별로 몇 개의 스레드가 생성되는지 확인한 결과를 볼까요.

Thread ID list of ParallelFor
1
2
3
4
5
6
7
8
9
10
--------------중략--------------
91
92
93
94
95
96
97
98
99
100
101
Thread ID list of RangeForEach
102
103
104
105
106
107
108
Press any key to continue . . .

구간 분할을 사용하는 경우(RangeForEach)는 고작 7개의 스레드가 생성되어서 작업을 처리한 것에 비해서, 로드밸런싱을 사용한 경우는 무려 101개의 스레드가 생성된 것을 볼 수 있습니다. 스레드가 많이 교체되면서 생기는 비효율성은 굳이 말을 안해도 되겠죵. 그렇다면 다음과 같은 코드로 실제로 어떤 속도의 차이가 발생하는지 확인을 해보시져 :)

using System;

using System.Linq;

using System.Threading.Tasks;

using System.Collections.Concurrent;

 

namespace Exam21

{

    class Program

    {

        static void NormalFor(int[] nums)

        {

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

            {

                nums[i] = nums[i] * nums[i];

            }

        }

 

        static void ParallelFor(int[] nums)

        {

            Parallel.For(0, nums.Length, (i) =>

            {

                nums[i] = nums[i] * nums[i];

            });

        }

 

        static void RangeParallelForEach(int[] nums)

        {

            var part = Partitioner.Create(0, nums.Length);

 

            Parallel.ForEach(part, (num, state) =>

            {

                for (int i = num.Item1; i < num.Item2; i++)

                {

                    nums[i] = nums[i] * nums[i];

                }

            });

        }

 

        static void Main(string[] args)

        {

            int max = 50000000;

            int[] nums1 = Enumerable.Range(1, 10).ToArray<int>();

            int[] nums2 = Enumerable.Range(1, 10).ToArray<int>();

            int[] nums3 = Enumerable.Range(1, 10).ToArray<int>();

 

            //JIT컴파일을 위해.

            NormalFor(nums1);

            ParallelFor(nums2);

            RangeParallelForEach(nums3);

 

            nums1 = Enumerable.Range(1, max).ToArray<int>();

            nums2 = Enumerable.Range(1, max).ToArray<int>();

            nums3 = Enumerable.Range(1, max).ToArray<int>();

 

            DateTime normalForStart = DateTime.Now;

            NormalFor(nums1);

            DateTime normalForEnd = DateTime.Now;

            TimeSpan normalForResult = normalForEnd - normalForStart;

 

            DateTime parallelForStart = DateTime.Now;

            ParallelFor(nums2);

            DateTime parallelForEnd = DateTime.Now;

            TimeSpan parallelForResult = parallelForEnd - parallelForStart;

 

            DateTime rangeForStart = DateTime.Now;

            RangeParallelForEach(nums3);

            DateTime rangeForEnd = DateTime.Now;

            TimeSpan rangeForResult = rangeForEnd - rangeForStart;

 

            Console.WriteLine("Single-threaded for : {0}", normalForResult);

            Console.WriteLine("Load-balancing Parallel.For : {0}", parallelForResult);

            Console.WriteLine("Range-partition Parallel.ForEach : {0}", rangeForResult);

        }

    }

}

좀 어글리한 코드이긴 하지만, 싱글 스레드를 사용한 for루프와 로드 밸런싱을 사용하는 Parallel.For, 그리고 구간 분할을 사용하도록 한 Parallel.ForEach의 성능을 각각 비교한 코드입니다. 그럼 결과를 확인해보죠!

Single-threaded for : 00:00:00.3000763
Load-balancing Parallel.For : 00:00:00.3430858
Range-partition Parallel.ForEach : 00:00:00.2130545
Press any key to continue . . .

조금은 의외의 결과가 나왔습니다. 오히려 병렬로 작업을 처리한 Parallel.For가 순차적으로 실행되는 for루프에 비해서 낮은 성능을 낸 것이죠. 왜 그럴까욤? 첫 번째 원인은 너무 빈번하게 스레드 간의 작업 전환을 하느라 오버헤드가 컸던 점을 들 수 있을 것 같구요. 그리고 다음은, 병렬 작업에 사용되는 델리게이트의 내용이 너무 짧아서 입니다. 위 코드를 보면 델리게이트의 내용이 간단한 연산 한 줄로 이루어진 것을 볼 수 있는데요. 위 처럼 호출 수가 작은 경우는 큰 문제가 되지 않지만, 위의 코드 처럼 많은 횟수를 호출하는 경우 델리게이트 호출은 부담이 됩니다. 델리게이트의 내용을 처리하는 것 보다 델리게이트를 호출하는 시간이 더 많이 걸리게 되면서 일반 for루프보다도 못한 성능을 보여주는 거죠. 그래서 Partitioner.Create메서드를 이용해서 큰 작업 덩어리로 잘라줘서 이 오버헤드를 줄일 수 있습니다. 그 결과 위와 같은 시간이 나오게 된거죠.

어떻나요? 단순히 for루프를 Parallel.For로 교체하는 것만 가지고는 제대로된 성능을 기대하기 힘듭니다. 혹시, "어? 뭐야 당신 이전 포스트에서는 단순히 Parallel.For를 쓰기만 해도 성능이 좋아진다며?" 하는 생각이 드셨다면... 당신은 저의 팬...???!?!? ㅋㅋㅋㅋ 아마 기억하시는 분은 없겠죠. 암튼, 주의하고 볼일 입니다. 오늘은 여기까징~! :)
저작자 표시 비영리 변경 금지
신고
지난 포스팅에서 Break와 Stop메서드의 차이에 대해서 시덥잖은 퀴즈를 남겼었는데욤, 뭐 별로 궁금한 분들이 없거나 너무 쉬워서 코에서 바람 좀 내뿜은 분들이 많으리라 생각합니다. 저는 이게 뭔 차이인지 잠깐 헤맸습니다만... 허허허 :) 그럼 차이점에 대해서 잠깐 이야기를 해볼까욤.


- Parallel.For와 for의 브레끼는 닮았더라.

Parallel.For의 Break메서드는 for의 break와 좀 닮았습니다. for루프에서 break를 만나게 되면 그 즉시 for루프를 중단하게 됩니다. 그리고 break가 걸리기 전 까지의 인덱스에 대해서는 루프가 실행되었음을 보장할 수 있습니다. for루프는 처음부터 순차적으로 실행되기 때문이죠. 그런데 Parallel.For에서는 루프문의 길이에 대해서 각각의 스레드가 조금씩 나눠서 담당하게 됩니다. 예를 들면 1-10000까지의 구간이라면, 스레드 하나가 1-5000까지, 다른 스레드가 5001-10000까지 뭐 이런식으로 말이죠. 이런 상황에서 5100번째 인덱스에서 Break가 호출된다면 어떻게 될까요? 기존의 for루프를 사용하던 관점에서 보자면 당연히 5109번째 인덱스까지는 다 처리되었을 거라고 예상하게 됩니다. 이런 개발자의 기대를 저버리지 않기 위해서 Break메서드를 호출하게 되면 Break가 호출된 인덱스보다 작은 인데스에 대해서 남아있는 작업은 모두 실행하도록 합니다. 예제를 볼까요?

using System;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Parallel.For(0, 20, (i, loopState) =>

            {

                if (i == 10)

                {

                    loopState.Break();

                    Console.WriteLine("Break at {0}", i.ToString());

                }

                else

                {

                    Console.WriteLine(i.ToString());

                }

            });

        }

    }

}


아주 간단하게 0부터 19까지 Parallel.For루프를 돌면서 i가 10일때 Break를 호출해서 루프를 중단하도록 하는 코드입니다. 실행할 때마다 결과가 다르게 나오는데요, 그 중에 가장 설명하기 좋은 결과를 고르면!

0

1

2

3

4

6

5

Break at 10

7

8

9

Press any key to continue . . .


대략 이렇습니다. 스레드가 구간을 나눠서 루프를 실행하는데, 먼저 실행한 스레드가 0-6까지를 처리하고 다른 스레드가 10을 처리하는 순간 Break를 만나게 되는 거죠. 하지만 여기서 중단 되지 않고, 다른 스레드가 7, 8, 9를 처리하도록 해줍니다. 그래서 10에서 Break가 호출되었더라도 바로 중단하지 않고 10보다 작은 인덱스가 모두 처리되도록 해주는 것이죠.


- 장비를 정지합니다... 어 안되잖아?

Break를 즉시 중단하는 개념으로 생각하다 보면 조금은 이상할 수 있습니다만, 사실 기존의 for루프와 유사한 동작을 제공한다는 개념에서는 바람직한 동작이 아닌가 싶습니다. 그런데, 데이터에서 뭔가를 검색하는 경우는 어떨까요? 4명이 근무하는 사무실을 생각해봅시다. 뭔가 중요한 서류를 찾아야 해서 사장 이하 4명이 구역을 나눠서 사무실을 뒤지기 시작합니다. 한 명은 책상을, 한 명은 캐비넷을, 또 한 명은 창고를, 또 한 명은 잉여짓을. 이렇게 4명이 찾다가 누군가 한 명이 그 서류를 발견했습니다!!! 그러면 그 사람은 '심봤다 어허으어릉러허엏엉 나 심봤쪄영 뿌우 'ㅅ'~'하고 외치겠죠? 그러면 그 사람들은 그 찾는 작업을 멈추게 됩니다. 그게 정상이겠죠. '그럴리가 없엉 어흐어흐어흐러라어헝'하면서 나머지를 계속 찾는 사람은 '안돼에~'. 허허허허 :)

이런 경우에는 Parallel.For를 즉시 중시해야 합니다. 그럴때! 바로 Stop을 호출하게 됩니다.

using System;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            Parallel.For(0, 20, (i, loopState) =>

            {

                if (i == 10)

                {

                    loopState.Stop();

                    Console.WriteLine("Stop at {0}", i.ToString());

                }

                else

                {

                    Console.WriteLine(i.ToString());

                }

            });

        }

    }

}

Break대신에 Stop을 호출한 코드죠. 결과능!

0

5

Stop at 10

Press any key to continue . . .


짤 없습니다. 그냥 중단해버리는 거죠. 자 지난 포스팅에서 왜 Stop을 호출한 게 미세하게 빨랐는지 아시겠나요? 소수는 1과 자신 외에 하나라도 나눌 수 있는 숫자가 존재하면 안되는 거죠. 그래서 for루프에서 숫자를 바꿔가면서 나눠보다가 하나라도 나눠지는 게 발견되면 그 즉시 중단하는 게 맞는 거죠. Break는 발견되더라도, 그 보다 작은 인덱스에 대해서는 모두 수행하게 되기 때문에 Stop에 비해서 아주 미세하게 느린 결과가 나오게 되는 거죵. 허허허허허 :) 참 별 내용 없네요 ㅠㅠ...

그럼 오늘은 여기까징~!
저작자 표시 비영리 변경 금지
신고

김명신님께서 또 아주 좋은 지적을 해주셨습니다~~ List<T>도 ConcurrentBag<T>이든 하나의 컬렉션을 이용하는 건 바람직하지 않은 것 같다는 의견이셨는데요, 동감합니다 :) 그래서 제 나름대로 해결책을 강구해봤는데욤. 스레드 로컬 변수를 이용해서 스레드 별로 작업을 시키고, 나중에 하나의 리스트로 모으는 것입니다. 코드를 볼까욤~?

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication1
{
    class Program
    {
        static readonly object _sync = new object();
 
        static IEnumerable<long> GetPrimeNumber(long num)
        {
            List<long> primeList = new List<long>();
 
            Parallel.For<List<long>>(0, num + 1, 
                () => new List<long>(), //스레드 로컬 변수 초기화
                (i, outerLoopState, subList) =>
            {
                bool isPrime = true;
                Parallel.For(2, i, (j, loopState) =>
                {
                    if (i % j == 0)
                    {
                        isPrime = false;
                        loopState.Break();
                    }
                });
 
                if (isPrime)
                {
                    subList.Add(i); //스레드 로컬 리스트에 추가
                }
 
                return subList;
            },
            (subList) => //각 스레드 종료 후에 취합
            {
                lock (_sync)
                {
                    primeList.AddRange(subList);
                }
            });
 
            return primeList;
        }
 
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            IEnumerable<long> primeList = GetPrimeNumber(99999);
            sw.Stop();
 
            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",
                sw.Elapsed.ToString(),
                primeList.Count());
            //뭔가 한다.
        }
    }
}

위 코드를 보면, Parallel.For 루프에서 스레드 로컬 변수를 사용하도록 밑줄 친 부분들이 추가된 것을 볼 수 있습니다. 즉 이 Parallel.For는 List<long>타입의 스레드 지역 변수를 사용하며, 그 지역변수를 어떻게 초기화 할 것인지를 명시하고, 각 스레드가 종료할 때 지역 변수를 리턴하도록 한 것이죠. 그리고 Parallel.For의 마지막 매개변수로, 각 스레드가 종료할 때 어떤 동작을 취할 것인가를 명시하는데, 스레드가 끝날 때 리턴한 지역 변수가 매개변수로 들어오게 됩니다. 이렇게 하면 동기화를 좀 더 줄일 수 있는 것이죠. 그래서, 이왕 코드를 고친 김에 시간을 재봤습니다.

   ConcurrentBag<T>사용(초)  스레드 지역 변수 사용(초)
 GetPrimeNumber(99999)  2.54  2.52 
 GetPrimeNumber(199999)  9.32  9.25
 GetPrimeNumber(299999)  20.03  19.96

미세하게 스레드 지역 변수를 사용한 쪽이 빠르긴 한데요, 위 코드의 경우 하나의 리스트로 값을 합치는 데 시간이 좀 소요되어서 큰 속도 향상이 없는 것으로 보입니다. 동기화해서 수행해야 하는 작업이 간단할 수록 속도차이는 벌어질 것으로 보이는데요, 가능하면 각자 작업 후에 하나로 합치는 쪽이 더 효율적일 것 같네요.

부족한 실력이다 보니 이 이상 뭐가 떠오르지 않네요 -_-;; 더 나은 방법이 있으면 알려주세용~ :)

저작자 표시 비영리 변경 금지
신고

신석현님께서 아주 좋은 지적을 해주셨습니다. 제가 예제로 사용한 코드를 돌리면 매번 prime count가 다르게 나온다는 것이었습니다. 참고를 위해서~~ 다시 그 문제의 코드를 붙여 보겠습니당. :)

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Break();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

 

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


이 코드에서 문제가 되는 곳은 바로~!!!!! List<T>를 사용했다는 점입니다. Parallel.For를 돌리면, 여러 스레드가 구역을 나눠서 동시에 Parallel.For안의 코드를 돌리게 되는데요. 이 과정에서 스레드 간의 충돌이 생겨서 그렇습니다. 문제를 찾아보기 위해서 리플렉터로 List<T>.Add(T)의 코드를 찾아보니...

public void Add(T item)

{

    if (this._size == this._items.Length)

    {

        this.EnsureCapacity(this._size + 1);

    }

    this._items[this._size++] = item;

    this._version++;

}


이렇게 되어 있습니다. 여기서 7번째 줄의 'this._size++'부분이 문제가 되는 것 같습니다. 제가 11번째 포스팅에서 적은 내용 처럼 말이죠. 그래서 size가 1증가 되기 전에 같은 같은 인덱스에 값이 두번 쓰여지는 것이 아닌가 하는 추측이 가네욤!!! :)

그래서 가설을 확인해보기 위해서 List<T>를 여러스레드가 동시에 사용가능한 ConcurrentBag<T>로 바꿔서 테스트를 진행해봤습니다. 그랬더니!! 매번 같은 prime count가 나오는 것을 볼 수 있었습니다. 하하하하하 ㅠ_ㅠ;; 참고로 ConcurrentBag<T>.Add<T>의 코드를 봤더니 내부적으로...

private void AddInternal(ThreadLocalList<T> list, T item)

{

    bool lockTaken = false;

    try

    {

        Interlocked.Exchange(ref list.m_currentOp, 1);

        if ((list.Count < 2) || this.m_needSync)

        {

            list.m_currentOp = 0;

            Monitor.Enter(list, ref lockTaken);

        }

        list.Add(item, lockTaken);

    }

    finally

    {

        list.m_currentOp = 0;

        if (lockTaken)

        {

            Monitor.Exit(list);

        }

    }

}


위와 같은 코드를 사용하더군요. Monitor를 사용해서 동기화를 시키고 있죠~? 하하하 :) 날카로운 질문을 해주신 신석현님께 감사드립니다 :)

저작자 표시 비영리 변경 금지
신고

자~ 그럼 이제 부터는 다시 병렬 프로그래밍으로 돌아가서 조금 이야기를 해보도록 할 까욤~? 오래전에 써놓은 자료를 보다보니..... 제가 쓴 내용이지만 무슨 내용인지 이해가 안되는 글이 많아서 다시 정리를 하느라 조금 애를 먹고 있습니다. 하하하하 :) 뭐 제 수준이 딱 거기 까지니까 말이죵 호호호호.


- 병렬 작업을 중단할 때능?

기존의 for루프를 중단할 때는 그냥 간단하게 break하나 추가해넣으면 되었습니다. for루프는 정해진 순서에 따라서 순차적으로 실행되기 때문에 특정 조건에서 break를 만나서 루프가 중단 된다고 하더라도 항상 취소되기 이전까지의 내용은 모두 실행이 되었음을 보장할 수 있었죠. 즉,

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

{

    if (i == 50)

    {

        break;

    }

}


위와 같은 코드가 있다고 할 때, i가 50이되어서 for루프가 중단된다고 할 때, i가 0-49일때는 이미 다 진행된 상태라는 것이죠. 그런데 이런 루프를 빠르게 처리하기 위해서 여러 스레드를 동시에 사용하는 Parallel.For를 사용했다고 한다면... 각 스레드가 나눠서 진행중인 작업을 어떻게 중단 시켜야 할까요?


- 브레이크를 걸자.

일단은 기존의 for루프와 가장 유사한 형태를 보이는 것 부터 살펴봅시당. 일단 비교를 위해서 다음과 같은 예제를 먼저 살펴보겠습니다.

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            for (long i = 0; i <= num; i++)

            {

                bool isPrime = true;

                for (long j = 2; j < i; j++)

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        break;

                    }

                }

                if (isPrime)

                {

                    primeList.Add(i);

                }

            }

 

            return primeList;

        }

       

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


아주 간단한 코드인데요, 1부터 사용자가 입력한 숫자 중에서 소수를 구해서 리스트에 추가하는 코드입니다. 그리고 각 수가 소수인지 검사하는 부분에서 1이 아닌 수로 나눠지는 케이스가 발견 되면 그 즉시 break를 이용해서 중단을 시키고 있죠. 위 코드를 실행하면 CPU점유율은 아래와 같이 단일 스레드를 이용하는 것을 볼 수 있습니다.


그리고 위 코드를 3번 실행해서 얻은 평균 실행 시간은 6.28초 입니다. 그렇다면 위 코드의 Parallel.For버전을 살펴 볼까욤?

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Break();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

       

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


for를 Parallel.For로 바꿨고, break역시 Parallel.For의 인자로 넘어오는 loopState변수에 대해서 Break메서드를 호출하는 것으로 변경되었습니다. 이 코드를 실행해보면, 아래와 같이 CPU의 4개의 코어가 모두 동작하는 것을 볼 수 있습니다.




그리고 역시 위 코드를 3번 실행해서 얻은 평균 실행 시간은 5.32 초입니다. 전에 비해서 약 15%의 성능향상이 있었습니다. 물론 소수를 구하는 알고리즘 자체를 더 개선할 수도 있지만, 단순히 싱글 스레드를 이용한 코드와 작업을 병렬화 시켜서 멀티 코어를 이용한 코드간의 차이가 이정도라면 의미가 있지 않을까요? 그리고 좀더 CPU를 많이 사용하는 코드일 수록 그 차이는 더 벌어지겠죠 :)


- 그런데 사실은...

위 코드에서 몇자를 바꾸고 나면, 코드의 평균 실행 시간이 5.29초로 조금 더 떨어지게 됩니다. 그 코드를 올려드릴 테니 어디를 수정한 건지 한번 찾아보시죵 :)

using System;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Diagnostics;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace ConsoleApplication1

{

    class Program

    {

        static IEnumerable<long> GetPrimeNumber(long num)

        {

            List<long> primeList = new List<long>();

 

            Parallel.For(0, num + 1, (i) =>

            {

                bool isPrime = true;

                Parallel.For(2, i, (j, loopState) =>

                {

                    if (i % j == 0)

                    {

                        isPrime = false;

                        loopState.Stop();

                    }

                });

 

                if (isPrime)

                {

                    primeList.Add(i);

                }

            });

 

            return primeList;

        }

        

        static void Main(string[] args)

        {

            Stopwatch sw = new Stopwatch();

            sw.Start();

            IEnumerable<long> primeList = GetPrimeNumber(99999);

            sw.Stop();

 

            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",

                sw.Elapsed.ToString(),

                primeList.Count());

            //뭔가 한다.

        }

    }

}


어딘지 찾으셨나요? 바로 loopState변수에 대해서 Break를 호출하던 것을 Stop으로 바꿔준 것입니다. 그래서 조금 더 효율적인 코드가 되는 것이죠. 왜 그럴까요? 그거에 대해서는!!!! 다음 포스팅에서 ㅋㅋㅋㅋㅋ


- 참고자료
http://stackoverflow.com/questions/1510124/program-to-find-prime-numbers

저작자 표시 비영리 변경 금지
신고

- 뭐 좀 더 편한 방법이?

지난 포스팅에서는 Monitor를 좀 더 간편하게 사용할 수 있게 해주는 lock구문에 대해서 살펴봤었습니다. 그런데... 더 편한 방법이 있다는 군요. 한 줄이면 된다고 합니다. 긴 말 필요없이 예제를 보죠!

using System;

using System.Threading.Tasks;

using System.Threading;

 

namespace Exam25

{

    class Program

    {

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

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

            {

                Interlocked.Increment(ref sum);

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

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

            {

                Interlocked.Decrement(ref sum);

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

위 예제는 정확하게 lock을 사용한 예제와 동일한 결과를 냅니다. Interlocked라는 클래스에 정의된 메서드를 통해서 1씩 증가시키고 감소를 시킨거죠. 즉, lock사용해서 명시적으로 락을 획득하는 대신에 스레드 안전한 방식으로 값을 증가시키고 감소시키도록 하는 것입니다. 그런데, 여기까지만 보면 약간의 의문이 생깁니다. 생각보다 더 간단해 보이지가 않는 다는 말이죠. 허허허허 :)

그런데 한 가지 장점이 더 있습니다. 바로 수행 속도 인데요. 잠금을 획득하고 해제하는 게 시간이 많이 걸리는 작업이다 보니, lock을 사용하는 것보다 Interlocked를 사용하는 게 조금 더 빠릅니다. 그럼 얼마나 더 빠르다는 건지 간단한 코드를 통해서 직접 확인을 해볼까욤?

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

namespace Exam26
{
    class Program
    {
        readonly static object sync = new object();
        static readonly int count = 10000000;
        static int sum = 0;       

        static void SpeedTest()
        {
            DateTime lockStart = DateTime.Now;
            for (int i = 0; i < count; i++)
            {
                lock (sync)
                {
                    sum++;
                }
            }
            DateTime lockEnd = DateTime.Now;
            TimeSpan lockResult = lockEnd - lockStart;

            sum = 0;

            DateTime InterlockedStart = DateTime.Now;

            for (int i = 0; i < count; i++)
            {
                Interlocked.Increment(ref sum);
            }
            DateTime InterlockedEnd = DateTime.Now;
            TimeSpan InterlockedResult = InterlockedEnd - InterlockedStart;

            Console.WriteLine("lock Result : {0}", lockResult);
            Console.WriteLine("Interlocked Result : {0}", InterlockedResult);
        }

        static void Main(string[] args)
        {
            //JIT 컴파일 수행
            SpeedTest();

            SpeedTest();
        }
    }
}


수행된 결과를 볼까요?

lock Result : 00:00:00.3270844
Interlocked Result : 00:00:00.1820249
lock Result : 00:00:00.3090990
Interlocked Result : 00:00:00.1820285
Press any key to continue . . .

두 번째 실행된 결과를 보면 Interlocked를 사용한 쪽이 70%정도 더 빠른 속도를 보이는 것을 할 수 있습니다. 위 코드 같이 단순한 연산이 많이 수행되는 경우에는 Interlocked를 사용하는 쪽이 조금 더 유리할 수 있겠죠. 물론 요즘 세상에 별로 신경쓸만한 차이는 아니라고 생각이 듭니다. 하하하하 그냥 이런 것도 있다는 거죠 :)

그럼, 오늘도 이만 하구요~ 다음에 또 뵙죵 하하하 :)

저작자 표시 비영리 변경 금지
신고
- 인류 발전의 원동력.

귀.찮.다. 이 세자로 이루어진 표현이 인류의 발전을 이끌어왔다면 확대해석일까욤. 정말 죠낸죠낸쵸오낸 귀찮다 보니 사람들은 어떻게 하면 반복적인 작업을 줄일 수 있을까 하는 문제를 생각하게 됩니다. 그래서 더 나은 방법을 고안해 내고, 더 빠르고 더 간편한 시스템, 그리고 나아가서 자동화를 생각하게 됩니다. 그러다 보니, 수작업으로 이루어진 프로세스는 실수의 여지가 많은 반면에 자동화로 이루어진 프로세스에서는 그 여지가 굉장히 줄어들게 되는 부수적인 효과를 얻게 되는 것이죠.

물론 귀찮음으로 인해서 인류는 많은 퇴보도 했을 겁니다. 복잡하고 비효율적인 것도 몸에 익으면 편하게 느껴지게 됩니다. 그리고 더 깔끔한 방법으로 바꾸려 하면 머리속에서는 엄청난 저항이 일어나게 되죠. 이미 한번 구성되어서 굳어진 뇌의 회로를 다시 구성해야 한다는 그...... 귀찮음. 허허허허허허


- 락에 자동화를 도입!

지난 포스트에서 Monitor를 이용해서 락을 거는 방법을 알아봤습니다. 하지만 어떤가요? 매번 try-finally블록을 사용해서 락을 걸고 해야 한다면? 그리고 Enter는 있는데 Exit를 깜빡하게 빼먹었다면? 어머나!!!!

그래서 C#에서는 귀찮음을 싫어하는 인류의 특성을 감안해서 간편한 Syntactic Sugar를 제공합니다. lock이라는 구문이 바로 그것입니다. 하하하하 :) 그럼 간단한 예를 보죠.

using System;

using System.Threading.Tasks;

 

namespace Exam24

{

    class Program

    {

        readonly static object sync = new object();

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

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

            {

                lock (sync)

                {

                    sum += 1;

                }

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

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

            {

                lock (sync)

                {

                    sum -= 1;

                }

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

어떤가요? Monitor를 사용해서 해주던 번거로운 작업이 lock이라는 구문으로 임계영역을 감싸주는 것으로 간단하게 해결 되었습니다. 자, 그럼 lock이라는 편리한 구문 뒤에 실제로 생성되는 코드를 한번 확인해 볼까욤?

private static void Main(string[] args)

{

    Task task = Task.Factory.StartNew(new Action(Program.IncreaseByOne));

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

    {

        object CS$2$0000;

        bool <>s__LockTaken1 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken1);

            sum--;

        }

        finally

        {

            if (<>s__LockTaken1)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

    task.Wait();

    Console.WriteLine("Result = {0}", sum);

}

 

private static void IncreaseByOne()

{

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

    {

        object CS$2$0000;

        bool <>s__LockTaken0 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken0);

            sum++;

        }

        finally

        {

            if (<>s__LockTaken0)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

}

위에서 볼 수 있듯이 생성되는 코드는 Moniter를 이용한 코드와 정확하게 같은 코드를 사용하고 있습니다. 대신에 훨씬 간단하죠? 물론 lock구문을 통해서 락을 걸어줄 객체는 Monitor를 사용할 때와 동일하게 readonly static이어야 합니다.
 
락을 거는 대상이 되는 잠금 객체에 대해서 조금 덧붙이자면, 락을 걸어줄 객체는 값 형(Value 타입)이 아닌 참조 형(Reference 타입) 객체여야 합니다. 왜냐면, 값 형의 경우는 다른 변수에 값을 대입할 때 값을 복사해서 넘겨 주기 때문에 매번 메모리에 동일한 값이 새로 생겨나게 됩니다. 이렇게 되면 Enter메서드에서 받는 잠금 객체와 Exit메서드에서 받는 잠금 객체가 서로 다른 객체가 되므로 SynchronizationLockException이 발생하게 됩니다. 반면에 참조 형 객체의 경우는 동일한 객체에 대한 참조를 복사해서 넘겨주므로 항상 동일한 객체에 대해서 Enter와 Exit를 호출하게 되겠죠.

그럼~ 다음에 또 뵙죵~ :)
저작자 표시 비영리 변경 금지
신고

우와~!!!! 증말 오랜만입니다. 이게 얼마만이죠? 하하하하하하하하-_-

기다리는 분이 아무도 없다는 건 알지만, 저 자신을 위해서 그냥 한번 호들갑을 떨어봤습니다. 허허허허 원래 제가 쓰는 포스팅이 다 그렇죠. 병렬 프로그래밍에 대해서 정리해놓은 자료가 조금 있었는데요, 매우 늦긴 했지만 그래도 일단 공부하면서 정리해놓은 자료를 그냥 썩히기는 아까워서 조금씩 옮겨 적어볼까 합니다. 그래서 다시 시작하는 거지요! 호호호호 :)

언제나 그렇듯이 제 포스팅은 공부하면서 정리하는 겁니다. 다른 분들이 적으시는 것 처럼 내공이 깊지도 않고, 그다지 실용적이지도 않지요. 의견이 있으시면 거침없이! 하지만 예의는 쪼금 지켜 주시면서! 가르침을 더 해주시면 읽으시는 분들에게 더 도움이 될 겁니다. 짧은 인생 Love & Peace! 그럼 또 초큼씩 풀어 볼까욤? ㅋㅋㅋ


- 화장실이 한 개!!!

4인 가족 기준으로 화장실이 한 개라고 생각을 하면..... 어머나!!! 끔찍해!!! 출근 시간이 되면 화장실에 응가를 때리러 러시아워가 벌어집니다. 먼저 들어간 사람은 느긋하게 다이빙하는 응가에 예술 점수를 매기면서 시간을 보낼 수 있지만, 출근 시간과 이제 그만 포기하자고 하는 장과 협상을 벌이면서 조마조마해야 하죠. 화장실을 원하는 사람은 4명인데, 화장실이 하나이다 보니, 먼저 들어간 사람이 화장실을 선점하고 문을 잠가서 락을 걸어버리면, 화장실을 원하는 다른 사람들은 그 락이 해제 될 때까지 인고의 시간을 보내야 하는 거죠. 어머나 슬픈이야기...ㅠㅠ

그러면 락을 걸지 못하게 하면 효과적인 해결책이 될까요? 일보고 있는데 갑자기 문을 열고 들어와서는 '난 얼굴 씻고 이빨만 닦으련다 변기를 쓰는 것도 아니니 너랑 내가 이 화장실을 평화롭게 공유하면서 각박한 출근 시간에 조그마한 훈훈함이라도 나눠가지지 않겠느냐' 하면 뭐 괘안을 수도 있겠습니다만.... 조금만 더 생각을 해보면 말이죠. 응가라는 건 굉장한 집중이 요구됩니다. 그리고 아무도 보는 사람이 없다는 안도감 속에서야 차분히 진행될 수 있는 일종의 비밀 의식 같은 거죠. 화장실을 공유하는 훈훈함 속에서 응가를 제대로 할 수 는 없습니다. 그리고 세면을 하는 사람 역시 은은하게 풍겨나오는 향기에 정신을 빼앗기다 보면 어서빨리 탈출하고 싶은 욕구에 사로잡히게 되어 세면을 제대로 할 수 없게 됩니다.

즉, 락을 거는 게 가장 효율적이라는 거죠. 여담이지만 그래서 저는 공중화장실에서는 응가를 안하는 편입니다. 정신 사나워서 집중을 할 수 없거든요. :)


- 그래서 필요한 동기화

그래서 프로그래밍을 하면서도 동기화에 신경을 써줘야 합니다. 컴퓨터의 자원을 한정적이고 그걸 호시탐탐 선점하려는 스레드의 욕구를 잘 제어해줘야 하기 때문이죠. 그럼 동기화 없이 스레드의 탐욕에 맡긴 결과가 어떻게 되나 한번 볼까요. 후후후후

using System;

using System.Threading.Tasks;

 

namespace Exam22

{

    class Program

    {

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

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

            {

                sum += 1;

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

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

            {

                sum -= 1;

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

두 개의 스레드를 이용해서, 주 스레드에서는 sum의 값을 1씩 감소 시키고, 추가 스레드에서는 증가 시키도록 해놓은 평범한 예제입니다. 하지만, 서로 뺏고 뺏기는 리얼 야생 탐욕이 살아 숨쉬는 정글의 법칙이 녹아들어 있습니다. 결과를 보시죠.

Result = 17104

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

분명히 같은 횟수로 1씩 증가시키고 감소를 시켰는데 이런 결과가 나오다니!!! 매번 실행 할때 마다 다른 값이 나오긴 하지만, 이 결과는 분명 이상합니다. 오래전 포스팅이지만 원자성에 대해서 잠깐 이야기를 한적이 있었는데요, 특정 작업이 실행 될 때 그 작업 외에 다른 작업이 끼어들어서는 안된다는 원칙을 이야기 하는 것입니다. 그렇다면! 위에서 사용중인 +=, -= 연산자는 분명히 원자성을 위반하고 있는 것으로 보입니다.

단순하게 'a += 1' 을 가지고 생각해볼까욤. 이 연산은 몇 가지 단계 나누어 집니다. 우선 a의 값을 가져오고, 가져온 값을 1 증가 시키고, 증가된 값을 다시 a에 대입하는 단계로 나누어 지는 거죠. 그런데 a값을 가져온 직후에 다른 스레드가 a의 값을 변화시킨다면 어떻게 될까요? 이런 거 말로 설명하면 설명하는 사람도 읽는 사람도 불통에 빠지게 됩니다. 현재 sum이라는 변수의 값이 5라고 가정하고 간단하게 표를 통해 설명 해보죠 ㅋ

 증가 스레드(sum += 1 수행) 감소 스레드(sum -= 1 수행) 
   5를 읽어옴
 5를 읽어옴  
 1증가시킴  
 6을 저장  
  1감소시킴 
  4를 저장 

위와 같은 순서로 수행이 된다면?

분명히 1증가와 1감소가 수행되었지만, 결과는 1감소만 수행된 셈이죠. 이런식으로 서로 방해와 방해를 하다보면 이상한 결과가 나오게 되는 겁니다. 그래서 화장실에 락을 걸어서 순서를 정해서 사용하도록 하듯이, 순서를 정해줘야 하는 겁니다. 하하하하 :)

- 모니터로 락을 걸자! 락을 걸자!

자 그럼 아주 간단하게, 모니터로 락을 걸어 볼까요? 준비물은 여러분이 지금 보고 있는 모니터를 사용하면 됩니....는 훼이크고 Monitor라는 클래스를 이용해서 락을 걸어보겠습니다. Enter와 Exit라는 메서드를 이용해서 락을 걸고 빠져나가도록 통제를 하게 되는 데요. 참고로 이렇게 둘 이상의 스레드가 동시에 접근하면 안되기 때문에 락을 걸어야 하는 부분을 임계여역(Critical Section)이라고 합니다. 허허허허. 그럼 Monitor를 사용한 예제를 한번 보시죰.

using System;

using System.Threading.Tasks;

using System.Threading;

 

namespace Exam23

{

    class Program

    {

        readonly static object sync = new object();

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

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

            {

                bool locked = false;

                Monitor.Enter(sync, ref locked);

                try

                {

                    sum += 1;

                }

                finally

                {

                    if (locked)

                    {

                        Monitor.Exit(sync);

                    }

                }

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

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

            {

                bool locked = false;

                Monitor.Enter(sync, ref locked);

                try

                {

                    sum -= 1;

                }

                finally

                {

                    if (locked)

                    {

                        Monitor.Exit(sync);

                    }

                }

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

자, 코드를 보면 락이 걸려있는지를 판별하기 위해서 readonly static으로 sync라는 변수를 선언하고, 그 변수에 대해서 락을 걸로 해제하는 것으로 특정영역에 대한 락을 걸도록 했습니다. 그리고 Monitor를 통해서 들어가고 나오는 순서를 정해주고 있죠. 만약에 주 스레드에서 sync에 대해서 락을 획득하고 sum을 감소시키는 중이라면, 다른 스레드는 sync에 대한 락이 해제 될 때까지 임계영역에 들어가지 못하고 대기하게 됩니다. 그렇다면? 각자가 원하는 작업을 하는 동안 다른 스레드가 방해를 하지 못하겠죵?

참고로 락을 걸고 해제하는 대상이 되는 sync라는 객체를 보면, readonly이므로 객체의 값을 변경시킬 수 없는 immutable한 객체이고, static이므로 유일하게 존재하게 됩니다. 이렇게 복사본이 없고, 값을 변경시킬 수 없는 객체에 대해서 락을 거는 것이 안전한 방법이지요. 위 코드를 실행하면 다음과 같은 결과가 나오게 됩니다.

Result = 0

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

이거시 바로 우리가 기대했던 결과지요. 하하하하하하하하. 아... 좀 불편한 자세로 포스팅을 하고 있으려니 허리가..... 일단 오늘은 여기까지 하겠슴니다 하하하하 :)

저작자 표시 비영리 변경 금지
신고

- Previously on TFS Thriller Ssoul.

쏘울의 컬렉션을 연결하는 데 성공한 보람과 세식. 덤앤 더머, 반짝반짝 모래알 같은 그들도 뭉치니 미션을 해결할만한 지혜는 나오는 듯 하다. 초보자를 고려한듯 친절하게 아주 천천히 조금씩 교모해지는 쏘울의 미션. 그들은 집에 갈 수 있을까?


- 신의 한 수.

자 이제 쏘울이 또 뭐라고 하는지 들어볼까?”

왠지 집에 가라고 할 거 같아서 두근두근 거리는데?”

퍽이나

fu**?”

? mot*** fu**?”

(저자 주 : 발음을 잘 해보시면, 이해 가능한 하급 말장난 패턴입니다. 방송심의위원회의 규정을 준수하기 때문에 별표처리한 것 양해바랍니다.)

폭풍 같은 개드립이 휩쓸고 지나간 자리는 적막만이 감돌았다.

우리 쏘울 이야기나 들어볼까.”

그러지

2번 동영상 파일을 재생했다. 이제 조금씩 익숙해지는 쏘울의 오토튠 목소리.

그래그래그래. 여기까지 잘 온 거 같군. 자네들도 바보는 아닌 거 같애. 자 그럼 이번에는 재밌는 퍼즐을 하나 풀어볼까? 나름 쏘우 패러디인데 퍼즐 하나 없으면 섭하겠지? 자 지금부터 퍼즐을 설명할 테니 잘 들으라고. 과제물 폴더의 Lab2폴더를 열어봐. 그러면 List라는 텍스트파일이 있어 그걸 열어봐.”


어때? 익숙한 C#코드가 보이지? 자 이제, 비주얼 스튜디오로 가서 아까 다운로드한 프로젝트를 열어봐.”


"
그래 내 애정이 담긴 환영 메시지가 보이겠지. 감동의 눈물은 잠시 접어두라고. 자 그럼 저 환영 메시지를 지우고, 아까 내가 준비한 텍스트파일 안의 내용을 거기다 붙여."


좋아 다 되었겠지. 이제 퍼즐을 설명해주지. 두 목록을 하나의 목록으로 합쳐야 하네. 아이디 목록의 첫 번째 항목과 이름 목록의 첫 번째 항목, 그리고 다음은 두 번째 항목끼리 그렇게 말이지. 그리고 합친 목록의 항목을 한 줄에 하나씩 출력하는 거야. 각 항목의 속성들은 쉼표로 구분해서 말이지. , 해서는 안 되는 게 몇 가지 있어. 작성한 코드를 절대로 지워서는 안되네. 한 번의 체크인과 두 번의 변경 내용 취소로 코드 작성을 완료해야 하네. , 백 스페이스 키나 delete키를 눌러서는 안 된다는 거지. 그리고 배열의 인덱스를 사용해도 안 돼. 그리고 마지막으로 Main메서드 안의 코드는 10줄 이내여야 하네. 이 제약사항만 지키면 되는 거지. 그럼 행운을 빌어.”

난생 처음 받아보는 황당한 요구사항에 둘은 잠시 생각에 잠겨 허우적 대고 있었다. 그리고 세식이 먼저 말문을 열었다.

그러니까 집에 안 보내 준다는 거네?”

너 예전에 천국이 너네 집이라고 했었지? 지금 당장 입주할래?”

왜 그러냐능!! 가정이 있는 몸 이해 못하냐능!!”

그래~ 빨리 집에 가야 되니까 이거부터 해결하자고~”

그래! 집에 가야지! 합시다!”

잠깐 생각하는 시간을 갖고, 이번에도 세식이 먼저 입을 열었다.

그럼 일단 지금 상태로 체크인 해두자. 코드 지우지 말라고 했으니까 초기 상태는 저장해놔야 다시 시작을 하지.”

, 좋은 생각이군.”


오케이 체크인은 됐고. 이제 둘을 합쳐야 되는데.”


무의식 중에 for문을 작성하던 보람은 소스라치게 놀랐다.

“꺄악!!! 어머나!!!!!!!!!”

야 인덱스 쓰지 말라 그랬자나~ 왜 그러셩~”

일단 다시 원상 복구 시키자고…”


오케이 다시 돌아왔다.”

이제 변경 취소 한번 남았군.”

두 목록을 합쳐야 하는데 인덱스를 사용하면 안된다라... 그럼 LINQ를 사용하라는 말인 거 같은데.”

“LINQ는 니가 더 잘알잖아. 뭐 좋은 거 없어?”

이게 두 목록을 하나로그리고 각 목록의 항목을 하나로 합치면서뭔가 기억날 듯 말 듯 하네.”

봐바. 여기 각 목록이 나란히 일렬로 있다고 했을 때, 순차적으로 각 목록의 항목이 하나씩 만나서 새로운 항목을 만든다는 거잖아? 이런 거 처리하는 거 없어?”

오호설명을 들으니 Zip이 생각나는데?”

그렇게 코드를 조심조심 심사숙고하며 작성해 나가기 시작했다.

우선 결과로 나오는 타입이 기존에 존재하지 않는 타입이니까 var타입으로 받고, 아이디 목록을 기준으로 이름 항목을 가지고 하나씩 붙이게…”


항목을 합치는 람다식이 첫 번째 목록의 항목과 두 번째 목록의 항목을 받아서 TResult라는 타입의 새 항목을 만드는 거니까…”


이렇게 작성하고, foreach로 출력하면 되겠군.”


~ 작성 끝? 확실한 겨?”

아마그렇지 않을까?”

떨리는 손으로 ctrl+F5를 누르자.


오케이. 떴다!”

…..”

근데 있잖아이거 샘플 데이터를 유심히 보니, 쏘울의 취향을 알 것도 같은데…?”

뭔데?”

일단, 콩과 2는 홍진호를 가리키는 거고, 한승연은 스타 팬들의 1대 아이콘, 아이유는 2대 아이콘 이었단 말이지. , 쏘울은 스타에 매우 열광하는 사람이 아닐까 하는 거고…”

원빈이랑 김두현은?”

글세 원빈은 잘 모르겠고김두현은 수원 블루윙즈 선수이고, 등번호가 4번이었단 말이지. 그러니까 수원 서포터가 아닐까 하는 생각이 드는 군.”

그래서 누군지 알겠엉?”

전혀.”

왜 아는 거 처럼 말해?”

어쩌라고.”

쏘울의 미션을 또 하나 해결한 보람과 세식. 과연 집에는 언제쯤

저작자 표시 비영리 변경 금지
신고
- Previously on TFS Thriller Ssoul.

덤 앤 더머 같이 심각한 상황에서도 농담따먹기 하면 클릭클릭으로 난관을 해쳐가던 보람과 세식. 그들 앞에 쏘울의 두번째 미션이 떨어졌다. 미리 만들어서 분리해놓은 팀 프로젝트 컬렉션이 있으니 그걸 연결하고, 그 안에 저장된 소스를 다운로드 하라는 것.


- 쏘울 컬렉션.


근데 팀 프로젝트 컬렉션이 뭐냐?”

글세 컬렉션이니까, 뭐 이렇게 모아놓은 걸 컬렉션이라고 하잖아? 그러니까 팀 프로젝트를 모아 놓은 걸 말하는 거 아닐까?”

오오미 세식이가 이렇게 똑똑할 줄이야.”

여러분 이게 바로 박세식입니다.”

적절한 패러디로군.”

내가 좀 적절하지. 야 그건 이제 됐고, 빨리 컬렉션 파일이나 찾아보자. 나 빨리 집에 가야 돼.”

보람은 세식의 귀소본능을 뒤로하고 C드라이브의 과제물과 Lab1폴더를 열었다.


"음
. 일단 데이터베이스 파일이네. 일단은 SQL서버에 연결해봐야 겠다."

여기 친절하게 매니지먼트 스튜디오 설치 파일도 같이 넣어놨고만. 웃어야 할지 모르겠다.”

보람과 세식은 우선 매니지먼트 스튜디오를 설치하고, Tfs_ssoul 파일을 데이터베이스에 연결하기 시작했다.


오케이 연결은 끝났고 이제 TFS에서 연결해야 되는데, 어떻게 해야 되는 걸까

여기 TFS 관리 콘솔 인가 하는 거 있네, 관리 작업이니까 여기서 하는 거 아닐까?”


오오미 좋은 생각이군. , 여기 팀 프로젝트 컬렉션 항목이 있네.”

여기 보니까 컬렉션 연결이 있는데? 이거 아닌가?”

눌러 보지 뭐


오 데이터베이스 자동으로 찾아주는데? 역시 로컬에 같이 깔아놓으면 편하구만


준비 검사 통과 좋고

오케이, 연결 완료. 안에 팀 프로젝트가 뭐가 있나 볼까.”


"이름 짓는 거 참 특이하고만
. ssoulTeam이라…"

일단 이거 비주얼 스튜디오에서 여는 게 급선무로군.”

“아! 나 예전에 누가 이거 쓰는 거 봤는데 팀 탐색기 안에서 연결하고 하는 거 같더라.”


"오호 좋은 정보 감사
. 팀 탐색기에.. 팀 프로젝트에 연결이라는 버튼이 있군."


오케이 이렇게 팀 프로젝트 컬렉션 선택하고 연결


와 연결 됐다. 이제 소스 코드만 다운 받으면 되지?”

. 소스 코드는 아마도 소스 제어쪽에서 가능할 거 같은데더블 클릭하면 열리겠지?”


오 열렸다.”

로컬 경로라는 부분이 눈에 확! 들어오는 데.”

나도 확! 들어온다. 눌러봐


오케이 맞는 거 같네. 로컬 경로 어디에 다운하라고는 안 했으니까 그냥 C ssoulTeam 폴더로.”

좋아 좋아. 끝이다.”

. 고생했네 친구.”

뭐 고생은 같이 했지.”

그럼 우리 이제 집에 갈 수 있는 거야?”

그러니까 그건 쏘울 마음이라고.”

과연 두 친구는 이 방을 나가서 집으로 갈 수 있을까.(BGM - 인생 극장 엔딩)

저작자 표시 비영리 변경 금지
신고
- Previously on TFS Thriller Ssoul.

이유도 모른 채 어둡고 축축한 방에 갇혀 쏘울이 내는 미션을 해결해야 하는 보람. 이해할 수 없는 문제는 이해하려고 노력하지 않는 것이 제 맛. 상황을 이해하기 보다 미션 해결을 하려하는 순간, 등 뒤에서 검은 물체가 부시럭 거리는 소리와 함께 움직였다. 과연 그 물체의 정체는.


- 검은 물체의 정체.

소리가 난 곳에서 뭔가 거대한 검은 물체가 일어났다. 옛날에 유행하던 유령의 모습 처럼 사람이 비닐을 뒤집어 쓴 것 같은 모양을 하고 있었으며, 몸뚱이에서 팔 같은 게 튀어나오기도 했다. 그리고 잠깐을 버둥거리더니

아 쫌, 이거 좀 벗겨달라고!!”

보람의 귀에 익숙한 소리가 들려왔다. 반신반의하며 살금살금 다가가서 비닐을 살포시 벗겨주었다.

? 야 너 여기서 뭐하냐

검은 물체는 보람의 친구인 세식이었다.

글세 나도 그게 궁금한데, 당췌 내가 왜 여기 있는 건지.”

일단 보람은 지금까지 자신이 보고 들은 것들을 세식에게 친절하게 알려줬다. 굳게 잠긴 문, 분해되어 있던 컴퓨터, 컴퓨터의 암호, 쏘울, 그리고 TFS.

그래서? 나 집에는 언제 갈 수 있는 건데?”

그걸 내가 알겠냐.”

!!! 와이프한테 또 늦게 들어온다고 혼나겠네~!!”

야 지금 늦게 들어가는 게 문제냐? 상황파악이 안돼? 우리가 이 방에서 나갈 수 있을지 없을지도 모르는 상황이라고

….. 우리가 누구한테 원한 살만한 게 있었나?”

글세. 원한이라. 이 정도의 사건에 휘말릴 만큼 센 원한은 없는 거 같은데.”

그럼 투한은?”

야 너 그냥 다시 비닐 뒤집어쓰고 있어라.”

그렇게 그들은 늘상 하던 무의미한 언어 유희질을 잠깐 나눈 뒤에 다시 축축하고 냄새나는 현실로 돌아왔다.

그래서 지금 쏘울이라는 놈이 여기다가 TFS를 설치하라고 했단말이지?”

그런 셈이지.”

근데 이거 윈도우 7이잖아? 여기에 TFS 설치되던가?”

되던가? 그래도 되니까 해보라고 한 거 아닐꺼나?”

그렇게 이야기 하며 ISO이미지를 마운트 시켰다. 그리고 설치 이미지의 폴더를 살펴봤다.


오 여기에 TFS-x64라고 있네, 여기 한번 들어가봐


역시 설치는 setup이 제 맛이지


야 여기 설치 기능 물어보는데?”

일단 잘 모를 때 설치는 무조건 전부 선택으로 하는 거야.”

그렇게 설치가 진행되고, 성공 메시지가 나타났다.

오오미 설치가 끝났군.”

구성 버튼을 누르자 구성 마법사가 시작되었다
.

야 보람 여기 몇 가지 옵션 있는데? 아마 우리는 기본 일까나?”

설명을 읽어보니 기본 맞는 거 같지?”


이 메시지는 뭐지…?”

글세? 윈도우 버전 한번 확인해봐.”

오호라, 이게 윈7 서팩1 이니까 TFS보다 나중에 나온 버전이라는 말이군? 뭐 문제 있겠어? 다음!”


SQL 서버가 안 깔려있으니 설치까지 해주나 본데?”

예전에 듣기로 TFS가 설치가 쫌 어렵다고 하던데 편해졌나 보군.”
 

통과 통과 좋아.”


완료 완료 좋아

그렇게 그들은 이 상황이 어떤 상황인지도 잊은 채로
, 통과와 완료에 들떠 있었다. 역시 남자는 장난감을 좋아하는 동물인 걸까.


오케이 서버는 끝난 거 같고, 뭐 빌드에 필요한 서비스가 있는 모양이군.”

근데 뭐 지금까지 그냥 클릭클릭하고 넘어왔잖아. 이거도 그럴 거 같은데.”


"클릭
."


"클릭
."


"클릭
."


"통과 통과"


"완료 완료"


그러게 진짜 클릭클릭 하면 통과 통과 완료 완료 고만

그런 이제 우리 집에 갈 수 있는 거야?”

글세그건 쏘울이 어떻게 나오냐에 따라 다르겠지.”

그렇게 보람은 동영상 파일이 있는 폴더를 열고
, 1번 파일을 재생했다. 다시 스피커에선 오토튠 목소리가 흘러나왔다. 그리고 이번엔 배경 음악도 깔려있었다.

안녕하신가 보람씨. 아 이제 세식씨도 일어났으려나? 어때? 배경 음악이 깔리니깐 오토튠에 대한 거부감도 줄어들지? 아까 0번 찍고 나서 모니터링을 해보니까 오토튠이 좀 귀에 거슬리더라구. 그래 이걸 봤다는 건 TFS설치는 성공했다는 거겠지? 물론 성공했겠지. 윈도우 7 TFS 설치는 그냥 클릭만 하면 넘어가는 거니까 말이지. 좋아 이제 또 과제를 주지. C드라이브에 보면, ‘과제물이라는 폴더가 있고 거기에 보면, ‘Lab1’이라는 폴더가 있지. 그 폴더 안에 보면, 내가 ssoul이라는 팀 프로젝트 컬렉션을 분리해 놓은 게 있어. 그걸 지금 설치한 TFS에 연결하고, 그 안에 있는 팀 프로젝트의 소스를 하드 디스크에 다운로드 해. 그게 다음 과제야. 그럼 잘 해결하길 바래.”

영상이 끝나고 세식과 보람은 잠깐 멍한 상태로 버퍼링을 하고 있었다
.

야 그럼 우리 집에 못 가는 거야?”

넌 집에 보물 숨겨놨냐. 왜 자꾸 집 타령이야.”

있지. 우리 아들 내미.”

"아들 내미 보고 싶으면 일단 과제부터 해결할 생각을 하자
. 안 그러면 여기가 우리 집이 될지도 모르니 말이지.”

커헉.”

저작자 표시 비영리 변경 금지
신고

- 본격(이라고 쓰고 막장이라고 읽는다) TFS 스릴러, 쏘울이란?

이 시리즈는 Team Foundation Server 2010을 주제로 연재되는 본격 스릴러입니다. 왜 아티클이 이딴 형식으로 연재되는가하는 생각을 가지신 분들이 있을 것 같습니다-_-. 저 자신도 글을 쓰면서 왜 이렇게 써야 하는지 의문을 가지고 있으니까요. 산을 힘들게 오르는 사람들에게 왜 그렇게 힘들게 산을 오르느냐고 물어보면, '산이 저기 있으니까 오른다'고 대답했다는 이야기가 있습니다. 진짜 인지 우스갯소리인지는 명확하게 알 수 없지만, 제가 이딴 본격 스릴러 형태로 TFS 아티클을 쓰는 것도 비슷한 이유입니다. 평범한 소시민 주제에 평범하게 하는 걸 별로 안 좋아하는 편이기 때문이죠. 이해가 되시나요? 아마 이해가 안 되실 겁니다. 한 철학자에게 '당신은 대체 누구요?'하고 물었더니 '나도 그게 알고 싶소!'라고 답변했던 것 처럼 말이죠. 아마도 이 시리즈가(글쓴이의 수준이) 막장이 될거라는 불안감을 해소하기 위해서 여러분의 정신을 혼미하게 만들고 싶은 마음이 있나 봅니다. 정신이 혼미한 상태에서 읽으면 글의 수준을 가늠하기 힘들기 때문이죠. 어헣.

아무튼, 이 시리즈는 계속해서 스릴러라는 장르를 유지한 채로 진행해볼 생각입니다. 어느날 갑자기 스릴러가 아니라 매뉴얼 형태로 글이 바뀌어 있다면, 저의 도전정신을 높게 평가해서 피식하고 웃어 넘겨 주시기 바랍니다. 허허허허. 이 시리즈의 대상은 TFS 2010을 시작하는 분들 입니다. 그럼. 이제 쏘울이 시작됩니다. 저와 함께 TFS를 만져보시죠 :)



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


축축한 공기가 기분 나쁘게 폐를 드나드는 불쾌한 느낌을 느끼며 그는 눈을 떴다. 그리고 그의 눈 앞에는 뭔가 이상한 장면이 눈앞에 펼쳐지고 있었다. 그는 할 말을 잃은 채로 방안을 둘러봤다. 한 쪽에는 이불 같은 것이 쌓여있고, 문은 철제 문으로 전자장치로 잠겨있는 것 같다. 그리고 창문은 아무 조그맣고, 오랫동안 청소를 안 한듯 먼지가 가득 껴있었다. 그리고 책상이 하나 놓여있다. 이렇게 방안을 둘러보고 나서야 스스로에게 질문을 할 여유가 생겼다.

여긴 어디지?’

생각의 끈을 잡으려 노력하면서 일어나려는데 뒷 목 언저리가 쑤셔왔다.

뭐지…?’

일어나려던 생각을 접고 앉아서 생각을 잡으려 노력했다. 그러자 어제 회사 회식이 있었고, 회식을 마친 뒤에 집에 가기 위해서 택시에 올라탔던 것이 기억났다.

설마납치 같은 건가?’

거기까지 생각이 미치자 일단은 방을 벗어나야 겠다는 결론에 이르렀다. 그리고 시선이 잠겨있는 문에 고정됐다.

저 문을 어떻게 하면 열 수 있을까?’

그렇게 문을 쳐다보며 분석을 시작했다.

쏘우 같은 공포영화에서나 나올법한 전형적인 문이군. 아마도 만지면 전기 충격이 오지 않을까?’

이런 생각을 하다가 문득, 자기 자신이 한심하게 느껴졌다. 그래서 손잡이에 손을 가져가면서 생각했다.

참 내이런 상황에서 잘도 그런 생각을 하는 구어러러호가올ㄴ기ㅏ헉!!!!!!!’

바닥에서 1분정도를 떼굴떼굴 굴렀을까. 정말 전기 충격이 있을 줄은 몰랐기 때문에 그는 무척이나 당황한 모습이었다. 도대체 이 상황은 뭘까. 그리고 정신을 차릴 무렵 방 한 구석에 큰 박스 하나가 보였다. 조심조심 박스로 다가서서는 박스를 열어봤다.

…? 이런 곳에 왜…?’

박스 안에는 컴퓨터 부품과 케이스, 그리고 모니터 및 기타 등등의 부품이 들어있었다. 그는 반사적으로 컴퓨터를 조립하면서 자신의 정체성을 분명히 하고 있었다. 그는 분명 컴퓨터와 매우 익숙한 환경에서 사는 사람이리라.

컴퓨터 조립을 끝내고 나자 그의 표정이 다시 어두워졌다. 사람은 뭔가 몰두할 일이 생기면 근심을 잠시 잊어버리는 법이다. 그래서 사람은 일에 미친 듯이 몰두하기도 하는 것이다. 현실에서 격리된 세상에서 머물기 위해서 말이다. 컴퓨터 조립에 몰두했던 그의 정신이 몰입에서 헤어나오는 순간, 불쾌하고 불확실한 현실과 다시 마주하게 되었다. 하지만, 그는 곧 자신이 몰두할 수 있는 일이 하나 더 있음을 발견했다.

좋아일단 전원을 켜보자고. 혹시 이 컴퓨터 안에 뭔가 있을지도 모르니 말이지.’

그렇게 책상 위로 컴퓨터를 옮기고 전원을 넣었다. 그랬더니 역시나 익숙한 윈도우 마크가 그를 반겼다.

뭐야다 세팅한 컴퓨터를 일부러 다시 분해해서 나뒀단 말인가꼼꼼한 놈이고만..?’

그렇게 윈도우가 부팅되고, 로그인 창이 나타났다. 그리고 순간 그는 멈칫했다. 로그인 창에 나타난 것은 그가 평소에 회사에서 사용하던 아이디였기 때문이다. 잠깐을 머뭇거리던 그는 익숙하게 비밀번호를 입력했고 윈도우는 환영합니다라는 메시지로 그를 반겼다. 그리고 바탕화면이 나타났다. 그의 눈이 조급하게 바탕화면을 훑었다. 그리고 그의 예상대로 바탕화면에는 하나의 폴더가 있었다. 폴더의 이름은 강보람씨 보셩’. 그렇다 이 남자의 이름은 강... 보람은 생각에 잠겼다.

내 회사 아이디와 비밀번호, 그리고 내 이름까지 알고 있다누굴까. 뭐 하자는 걸까.’

폴더를 열자 동영상파일이 여러 개 나타났다. 동영상 파일에는 번호가 붙어있었다. 침을 한번 삼키고, 0번 파일을 열었다. 그리고 스피커에서 왠 오토튠 처리된 목소리가 흘러나왔다.

안녕 보람씨. 당신이 왜 여기에 갇혀있는지 궁금하겠지. 하지만 그건 천천히 이야기 하자구. , 우선 내 소개를 먼저 할까? 내 이름은 쏘울. 이름을 들으면 삘이 딱 오지? 그래 바로 그 쏘우의 모방 범죄를 하고 있는거지. 낄낄낄. 그래 쏘우를 봤으면 이제 자네한테 어떤 일이 닥칠지는 예고편을 안 보여줘도 잘 알 수 있겠지?”

오토튠으로 내는 소리를 무반주로 듣고 있자니 환장할 노릇이었다. 하지만, 보람은 지금 그런 걸 가릴 처지가 아니었다. 당장이라도 멱살잡이 하고 오토튠에서 해방되고 싶지만, 상대는 모니터 안에 있으니 할 수 없는 법.

그래 이제 슬슬 미션이 뭔지 궁금해지겠지. 쏘우에서도 미션이 주어지고 성공하는 사람만 생존했으니 말이지. 자넨 프로그래머니까 좀 프로그래머 다운 미션을 주지. 자 그럼 이제 우리 즐거운 첫 번째 미션을 수행해 볼까? 자네가 지금 영상을 보고 있는 컴퓨터는 Windows 7이 깔려있어. 그리고 폴더에 필요한 파일도 ISO 이미지로 준비가 되어 있지. 우선 여기에 TFS가 동작할 수 있게 설치하는 것으로 첫 번째 미션을 시작해보자고. , 그리고 노파심에서 하는 이야기 인데, 내가 늘 지켜보고 있으니까 말이지 농땡이나 허튼 생각은 하지 말라고. 미션을 잘 수행해주면 그 방에서 나올 수 있게 해줄게.”

영상이 끝나고 보람은 멍한 상태로 모니터를 쳐다보고 있었다.

? TFS? 미션? 쏘울?!?!?!?! 스토리는 막장 드라마 수준이구만그래 일단 방법이 없는 거 같으니 해보자고.”

그렇게 단념하고 폴더에서 ISO이미지를 찾아서 마운트 하려는 순간 등 뒤에서 부스럭 거리는 소리가 났다. 깜작 놀란 나머지 자리에서 팔짝 뛰어서 떼굴떼굴 구르고 소리가 난 쪽을 쳐다보았다. 그리고 소리가 난 곳에서는


- 차회예고

부스럭 거리는 소리에서 나타난 것은 거대한 검은 물체. 과연 그 물체는 무엇일까. 과연 보람은 그 검고 거대한 물체와 쏘울의 미션에서 살아남을 수 있을까?

저작자 표시 비영리 변경 금지
신고

그간 저희 Visual Studio Korea 팀에서 2010년 6월 1일 REMIX10 무료 온라인 백서를 참석 전원에게 드린 적이 있었습니다.

http://vsts2010.net/338
Visual Studio 2010 최신 PDF 자료를 MSDN 에서 다운로드 받으세요

그리고 지난 2011년 4월 18일, 그 두 번째 온라인 무료 백서를 공개하게 되었습니다.

VISUAL STUDIO KOREA 팀의 온라인 백서 다운로드 사이트

http://msdn.microsoft.com/ko-kr/gg620748

Visual Studio 응용 프로그램 모델링 완전 정복 백서

엄준일 MVP (엔씨소프트) – 다운받기

"Visual Studio 응용 프로그램 모델링 완전 정복 백서" 개발자에서 뛰어난 개발자로 안내하는 효과적인 백서입니다. 최종 산출물인 동작하는 소스 코드를 위해, 모델링에 대한 배경과 방법을 개발자의 관점에서 설명합니다. 그리고 Visual Studio 2010 이용하여 개발자가 효과적으로 모델링을 있는 환경을 제시합니다.

개발자들이여, 이제는 "개발자는 코드로 말한다" 관념을 버리십시오.현대의 소프트웨어 개발에서는 언제까지 당신의 코드가 나오기까지 기다려주지 않습니다.선택과 집중의 개발 생태계에서 당신이 올바른 방향을 바라보고 발을 내딛는다는 것을 아무도 믿어주지 않습니다.

코드로 말하기 이전에 자신의 목적과 목표를 명확하게 시각화하여 말하는 것이야 말로 우리 시대가 원하는 뛰어난 개발자임이 확신합니다.

ASP.NET MVC - M, V 그리고 C 각방생활

박세식 (유니위스) - 다운받기

ASP.NET 가려운 곳을 긁어줄 대안의 프레임워크가 나왔으니 바로 ASP.NET MVC 프레임워크입니다. MVC 각각 담당하고 있는 일이 있습니다. 컨트롤러는 사용자 요청의 흐름을 제어하고 그에 따른 모델과 뷰를 선택하는 , 모델은 데이터와 유효성 검사, 비즈니스 로직을 담당하는 , 뷰는 컨트롤러에서 전달받은 데이터를 UI에서 처리하는 일을 합니다. 이렇게 모델, , 컨트롤러로 명확하게 분리된 구조가 여느 복잡한 어플리케이션도 구조적으로 쉽게 개발할 있도록 도와줍니다. 여기 백서를 통해 개발의 도움을 주는 M, V 그리고 C 각방생활을 소개합니다.

남자의 Visual Studio 2010 TDD(Test Driven Development)이야기

강보람 MVP (IT Flow 선임 컨설턴트), 박세식 (유니위스) - 다운받기

TDD(Test Driven Development), 테스트 주도 개발은 애자일한 개발을 지원하기 위한 하나의 실천적 도구입니다. 하지만, 단순히 세부적으로 어떻게 해야 되는 것인지를 묻는 'How'만으로는 TDD 제대로 이해할 수가 없습니다. 어째서 TDD 소개되었으며, TDD 통해서 어떤 장점을 얻을 있는지를 이야기 하는 'Why' 'What' 동반되어야 TDD 이해할 있는 기반을 마련하는 것이시죠. 여기 개발자가 TDD 대해 나눈 대화를 흥미롭게 재구성해 기록한 백서가 있습니다. 백서를 통해서 TDD 대한 'Why', 'What' 그리고 Visual Studio 2010 개발에 TDD 적용한 'How' 같이 얻으시기 바랍니다.


저작자 표시 비영리 동일 조건 변경 허락
신고

- K의 Visual Studio 이야기

출근 인파를 뚫고 회사에 출근한
K. 인파를 뚫고 지나오느라 쌓인 스트레스를 녹이기 위해 사온 캔 커피의 뚜껑을 따고 한 모금 마신다. 커피가 몸에 퍼지면서 안도감이 밀려올 쯤, K씨는 어제 마무리 못한 일이 떠올랐다.
 

'
아 참, 어제 코드 짜다가 말았지.'

그리고 모든 닷넷 개발자가 그렇듯이 컴퓨터가 부팅되자 마자
Visual Studio 2010을 실행시킨다. 잠시 후 Visual Studio 2010의 시작 페이지가 K를 맞이하고, K는 어제 작업하던 코드를 불러온다. 알록달록한 색깔이 코드 편집기가 나타나고, K는 잠시 생각에 잠긴다.

'
어제 어디까지 했더라?'


K
Ctrl키와 2를 함께 눌러서 현재 편집 중인 파일에 있는 모든 클래스 및 메서드의 정보를 표시한다. 그리고 각 메서드의 이름과 메서드의 파라미터 및 리턴타입을 보면서 어제 작업했던 내용을 찬찬히 머리속에 복기한다. 그리고는 어제 새 클래스를 추가해야 했다는 사실을 떠올린다. 그래서 Ctrl키와 3을 함께 눌러서 Quick Access창을 띄운다.


그리고
'클래스 추가'을 입력하고 결과로 검색된 '클래스 추가'를 선택한다. 그리고 이어서 나타난 '새 항목 추가'창에서 클래스 이름을 입력하고 클래스를 추가한다. 그리고 또 하나의 클래스를 추가하기 위해서 다시 Ctrl 3을 누르자,


검색을 하지 않아도 최근에 사용한 커맨드 목록이 나타나서 수고를 덜어준다
.
그리고 작업이 길어지면서, 파일을 여러 개 넣어놓게 되자 필요한 파일을 찾는 것이 힘들어 졌다. 그래서 가장 중요하게 계속 확인해야 하는 파일의 탭을 고정시켜서 항상 나타나도록 했다.


필요한 탭이 고정되면서 아무리 파일을 많이 열어도 필요한 파일만 쉽게 찾아볼 수 있으며
, 다른 프로젝트에 속한 탭들은 다른 색깔로 구분이 되기 때문에, 쉽게 각 프로젝트의 파일을 찾아서 작업할 수 있어서 편리하다.


그리고 작업중에
Get~으로 시작해서 ~Command로 끝나는 메서드를 찾아야 했다. 그래서 K Ctrl F키를 눌러 검색창을 띄우고, 'Use Regular Expression'을 선택하고 정규식을 입력해서 검색을 실행했다.


그리고 검색된 결과, 해당되는 것들이 어디에 있는지 스크롤바에 나타난 노란 점을 통해 알 수 있었다.


그리고 그곳에 마우스를 올려서 어떤 코드인지 프리뷰를 통해서 확인했다
.


이렇게 작업을 하면서
, K Productivity Power Tool 덕분이 Visual Studio 2010을 좀 더 편리하게 사용하면서 작업을 이어나갔다.


- 확장 관리자로 편리하게 사용하는 Visual Studio 확장 앱


위 시나리오는
Visual Studio 2010의 기능을 확장해서 더 편리한 기능을 제공할 수 있도록 하는 확장 앱 중에 가장 많이 사용되는 Productivity PowerTool을 사용한 것입니다. 위에서 볼 수 있듯이 Visual Studio 2010의 부족한 기능을 채워 더 편리하게 IDE를 사용할 수 있도록 해주는 것이죠. 이 확장 앱을 사용하는 방법은 어떨까요? 매우 간단합니다!


Visual Studio 2010
도구메뉴에서 확장관리자를 선택하고


확장 관리자 대화상자에서
온라인 갤러리를 선택하면 바로 설치해서 사용가능한 확장 앱의 목록을 확인할 수 있습니다. 그리고 오른쪽의 항목을 선택하고 더블클릭하면 바로 설치해서 사용할 수 있는 것이죠. 그리고 확장 앱이 업데이트가 되면, Visual Studio에서 확장 앱의 업데이트가 있음을 알려주고, 즉시 설치해서 업데이트된 앱을 사용할 수 있습니다. 매우 편리하죠~? 그러면 Productivity Power Tool을 제외한 다른 앱은 어떤 게 있는지 간단하게 살펴볼까요~?


- VS10x Code Map v2


코드의 구조를 시각화해서 파악하기 쉽도록 해주는 확장 앱입니다
. 코드를 파악할 때, 코드를 이리저리 확인해볼 필요 없이 한 눈에 코드의 구조를 파악할 수 있도록 해주는 것이죠. 게다가 주의가 필요한 곳에는 위 그림에서 볼 수 있듯이 책갈피를 끼워놓을 수가 있습니다. 편리하겠죠?


- VS Commands 2010


VS Commands 2010
Visual Studio 2010에서 아기자기한 기능을 많이 제공합니다. 위 그림에서 볼 수 있듯이 CSS파일의 색상이나 XAML코드의 색상을 입력할 때, 색상코드가 무슨 색인지 바로 확인할 수 있도록 도와주는 것 같이 말이죠.


- PowerCommands For Visual Studio 2010


PowerCommands
역시 매우 유용한 기능을 많이 제공합니다. 기존에는 프로젝트 단위로 폴더를 열 수 있었지만, 파일의 위치 폴더를 열 수 있으며, 항목이 위치한 폴더 위치에 대해서 Visual Studio 명령 프롬프트를 열 수 있습니다.


- Visual Studio Color Theme Editor


이 앱은
Visual Studio 2010의 일률편천적인 모습이 질리신 분들께 재미를 드리는 앱입니다. 선택할 수 있는 여러 테마를 제공하며, 사용자의 마음대로 재정의할 수 있는 기능도 제공합니다. 역시 Visual Studio를 WPF로 만들어 놓으니 이런 장점도 가져갈 수 있군요!


- 입맛에 맞는 Visual Studio 앱으로 편리한 개발을!

위에서 살펴본 확장 앱들은 일부에 불과합니다. 더 많은 앱이 사용자의 선택을 기다리고 있죠. 손에 착착달라 붙는 앱을 골라서 더 편리한 Visual Studio를 사용하시기 바랍니다!

저작자 표시 비영리 변경 금지
신고

인텔리 트레이스를 보면, 상황에 따라 정말 편리하게 사용할 수 있게 잘 만들어져 있습니다. 그런데, 다양한 이벤트를 로그에 남길수 있도록 지원하고 있지만 목록에 없는 이벤트를 어떻게 잡아야 할까요? 예를 들어서 우리 팀이나 내가 자주 사용하는 메서드를 이벤트 로그에 기록을 자동으로 남기고 싶을 때 말이죠.

예를 들어서, 다음과 같은 클래스 라이브러리 프로젝트가 있습니다. 이 메서드가 우리 팀에서 자주 사용하는 메서드이며, 이벤트 로그에 자동으로 남도록 하고 싶다고 가정해보겠습니다.

namespace BoramLib
{
    public class Logger
    {
        public static void Herehere(string msg)
        {
        }
    }
}

즉, 위와 같은 메서드가 실행 될때 마다 자동으로 인텔리 트레이스에 이벤트 기록으로 남기려고 합니다. 그런데, 인텔리 트레이스는 이벤트 셋을 수정할 수 있는 공식적인 방법은 제공하지 않습니다. 그래서 약간의 설정 작업을 통해서 이벤트를 추가해야 하는데요. 지금부터 한번 살펴보시죠.

'C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\TraceDebugger Tools\ko'폴더를 찾아가시면, 인텔리 트레이스의 모든 설정이 담긴 CollectionPlan.xml이라는 파일이 있습니다. 이 파일을 Visual Studio에서 엽니다. 그리고 'TracePointProvider'항목 안에 'Categories'항목을 찾습니다. 그리고 그 항목 안에 다음과 같이 커스텀 이벤트 카테고리를 추가합니다.

<Category Id="BoramLib.Logger" _locID="category.BoramLib.Logger">Logger</Category>

설정 파일을 저장한 후에, Visual Studio를 재 시작해서, 인텔리 트레이스의 옵션으로 가보면,


위와 같이 Logger라는 이름으로 카테고리가 추가되었습니다. 이제, 실제로 이 카테고리에서 어떤 모듈에 대해서, 어떤 메서드의 이벤트를 잡으며, 어떻게 출력할 것인지를 설정하도록 하겠습니다.

CollectionPlan.xml에서 'ModuleSpecifications'항목 안에 다음과 같이 dll이름을 명시해줍니다.

<ModuleSpecification Id="BoramLib.Logger">BoramLib.dll</ModuleSpecification>

그리고 BoramLib.dll을 'C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\TraceDebugger Tools'에 복사해 넣습니다.


그리고 CollectionPlan.xml에서 'DiagnosticEventSpecifications'항목을 찾아서 다음과 같은 항목을 추가합니다.

<DiagnosticEventSpecification>
  <CategoryId>BoramLib.Logger</CategoryId>
  <SettingsName _locID="settingsName.BoramLib.Logger.Herehere">Herehere!!</SettingsName>
  <SettingsDescription _locID="settingsDescription.BoramLib.Logger.Herehere">
      여기서 Herehere 메서드가 실행되었습니다.</SettingsDescription>
  <Bindings>
    <Binding>
      <ModuleSpecificationId>BoramLib.Logger</ModuleSpecificationId>
      <TypeName>BoramLib.Logger</TypeName>
      <MethodName>Herehere</MethodName>
      <MethodId>BoramLib.Logger.Herehere():System.Void</MethodId>
      <ShortDescription _locID="shortDescription.BoramLib.Logger">
           Herehere "{0}"</ShortDescription>
      <LongDescription _locID="longDescription.BoramLib.Logger">
           여기서 Herehere 메서드가 실행되었습니다. "{0}"</LongDescription>
      <DataQueries>
        <DataQuery index="0" maxSize="4096" type="String" name="로그 메세지" 
            _locID="dataquery.BoramLib.Logger.Msg" _locAttrData="name" query=""></DataQuery>            
      </DataQueries>
    </Binding>
  </Bindings>
</DiagnosticEventSpecification>

인텔리 트레이스 세부 항목 이름(SettingName)과 타입 이름, 메서드 이름, 인텔리 트레이스 이벤트 제목(ShortDescription), 이벤트 상세 내용(LongDescription)등을 기록합니다. 그리고 메서드의 리턴 타입이나 매개변수등을 DataQuery항목에서 잡아서, 메세지에 출력하도록 할 수 있습니다. DataQeury의 속성 중에 중요한 것에 대해서 좀 더 자세한 설명을 드리면요~,

- index : 필수 속성이며, -1이면 리턴 값을 나타내며, 인스턴스 메서드의 경우는 0이 인스턴스를 가리키며, 1부터 차례대로 매개변수를 가리킨다. static 메서드의 경우는 0부터 차례대로 매개변수를 나타낸다. 즉, 위 Herehere메서드는 static메서드 이므로 0번은 msg 매개변수를 가리킨다.

- type : 인덱스에 해당하는 값의 타입을 명시하며, Boolean ,Char ,Double ,Single (float) ,Int16 ,UInt16 ,Int32 ,UInt32 ,Int64 ,UInt64 ,String타입이 명시 가능하다. 이 값에 포함되지 않는 타입은 IProgammableDataQuery를 구현하는 다른 방법을 사용해야 한다. 

이런 설정을 통해서 메세지에 포함할 값을 명시할 수 있는 것이죠. 설정을 저장한 후에, Visual Studio를 재시작 합니다. 이제 커스템 이벤트를 잡을 준비는 마쳤으므로, 실제로 커스텀 이벤트를 잡는 코드를 작성해보겠습니다. 간단한 콘솔 어플리케이션을 생성한 다음에, BoramLib.dll을 참조추가합니다.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            BoramLib.Logger.Herehere("프로그램 시작");

            BoramLib.Logger.Herehere("프로그램 끗");
        }
    }
}

위와 같이 Herehere메서드를 두번 사용하고 나서,


옵션에서 Logger의 Herehere!!항목이 선택되어 있는지 확인합니다. 그리고 Main메서드의 닫는 중괄호(})에 중단점을 놓고 디버깅을 시작합니다.


그러면 위와 같이 인텔리 트레이스의 이벤트 로그에 Herehere메서드가 잡힌 것을 볼 수 있습니다. 이렇게 여러분이 직접 필요한 메서드에 대한 이벤트를 잡도록 설정해서 로그를 활용할 수 있습니다.


- 참고자료

1. http://blog.qetza.net/en/2010/03/08/vs-2010-personnalisation-des-vnements-de-lintellitrace/
저작자 표시 비영리 변경 금지
신고

개발을 진행하다보면, 아무리 훌륭한 툴이라고 하더라도 기능이 부족해서 확장 컴포넌트를 구매해서 쓰는 경우가 있습니다. 저도 예전에 Visual Studio 2008로 개발할 때, DB의 변경사항을 LINQ to SQL의 OR디자이너에 바로 반영하는 기능이 없어서, 검색하다 찾은 확장 컴포넌트를 체험기간만큼 사용했던 기억이 있습니다. 비주얼 스튜디오 2010의 IDE가 매우 훌륭해지기는 했지만, 그래도 좀 더 손맛이 느껴지는 기능을 찾으시는 분들이 있으실 텐데요, 아주 훌륭하고도 공짜인 확장 툴이 가까이 있습니다. 오늘 소개해드릴 Productivity Power Tools가 바로 그것입니다!

설치부터 아주 간단합니다. '도구' 메뉴의 '확장 관리자'를 이용하셔서 '온라인 갤러리'에서 'power tool'이라고 검색만 하시면 찾을 수 있고, 더블클릭만 해주시면 그냥 쉽게 깔립니다. 하지만 기능은 매우 다양합니다. 이제 부터 하나하나 소개 해드리겠습니다.


- 솔루션의 대양을 항해하라, Solution Navigator

좀 복잡한 프로젝트를 하다보면 솔루션에 프로젝트가 아주 많아집니다. 저는 16개정도까지 포함되는 걸 해본적이 있는데요, 이런 솔루션의 대양을 항해할 때는 역시, 나침반이 제맛이죠 :)


어떤가요? 솔루션 탐색기랑 비교해서 좀 이쁘게 생겼나요? 우선 뭔가 틀린 점들이 보이는데요, 하나씩 알아보죠.

1. 클래스의 멤버레벨까지 탐색.


기존의 솔루션 탐색기는 파일레벨까지 탐색이 가능했지만, Solution Navigator(이하 네비로 줄여씁니다)에서는 클래스의 멤버까지 탐색이 가능합니다.

2. 빠른 검색.

네비를 보면, 검색어를 입력할 수 있는 텍스트박스가 하나 있습니다. 여기에 Cl이라고 입력하면(씨엘...?), 다음과 같이 Cl이 포함되는 항목을 모두 찾아줍니다.


3. 호출 계층구조 바로 보기

Visual Studio 2010에 추가된 호출 계층구조 보기가 네비안에 통합되어 있습니다. 네비안에서 항목을 하나 선택하면, 오른쪽 끝에 조그만 아이콘이 하나 생깁니다. 이 아이콘을 누르면, 네비의 항목이 그 항목을 원점으로 해서 다시 표시됩니다. 예를 들어서 Class1의 PrintCount()메서드를 원점으로 해서 보면 아래와 같습니다.


이 메서드가 포함하는 것과, 참조하는 것들, 누가 이 메서드를 호출하는지, 이 메서드가 어떤 것들을 호출하는지 등을 한번에 파악할 수 있죠.

4. 네비가 표시할 항목 필터링

네비에서 솔루션항목 바로 아래에 보면 4가지 커맨드가 있습니다. All은 말 그대로 모든 항목을 표시하는 거구요, Open은 아래처럼, 지금 코드 편집기에서 열려있는 항목만 표시합니다.


그리고 Unsaved는 아래처럼 변경사항을 저장하지 않은 항목만 보여줍니다.


저장하지 않은 항목의 탭을 붉은색 점으로 강조하는 것도 새로 추가된 기능입니다. 그리고 Edit는 한번이라도 수정되었던 파일을 표시합니다.

5. 이미지 썸네일 제공

네비에 에서 솔루션에 포함된 이미지 파일에 마우스를 올리면, 아래와 같이 이미지의 썸네일을 표시해줍니다.


6. 코드 편집기에서 즉석 호출

코드 편집기에서 네비의 기능을 즉석 호출할 수도 있는데요, 예를 들어서 PrintCount메서드내에 커서를 두고 'Ctrl + 1'키를 누르면, PrintCount메서드와 관련된 항목들이 바로 나타납니다.


그리고 'Ctrl+2'를 누르면, 현재 코드 편집기에서 열려있는 파일의 모든 클래스에 대한 정보가 바로 나타납니다.




- Visual Studio를 Tab!

비주얼 스튜디오의 코드편집기에서 여러파일을 작업하려면 여러파일을 열어놓고 탭으로 파일간의 이동을 하면서 작업을 해야 합니다. 그만큼 많이 사용해야 하는 탭인데요. 이 탭이 어딘가 모르게 좀 불편한 면이 있었습니다. 그래서 Power Tools에서는 이 탭에 대한 지원이 막강해졌습니다.

1. 다양해진 옵션


옵션에 들어가면 Power Tools탭안에 Document Tab이라는 항목이 따로 있습니다. 그리고 미리 정의된 프리셋을 제공하는데요, Visual Studio 2008부터 아주 다양한 프리셋을 지원합니다.

2. Option 1 : Web Browser: Scrollable tab well


이 탭은 브라우저 처럼, 탭에 해당항목의 아이콘이 붙습니다. 그리고 고정시킬 수도 있는데요, 고정시킨 탭은 움직일 수 없습니다. 그리고 'Close All But Pinned'와 같이 고정된 탭을 제외하고 모두 닫기 같은 옵션도 제공합니다. 그리고 옵션에서 'Show pinned tabs in a separate row'를 체크하면 아래와 같이 고정된 탭만 구분해서 볼 수도 있습니다.


3. Option 2 : Scrollable Tab Well: Sorted by project


이 옵션을 사용하면, 위 사진과 같이 프로젝트 별로 탭의 색깔을 구분하고 정렬해서 보여줍니다. 15개까지 다른 프로젝트를 지원합니다.

4. Option 3 : Vertical Tab Well: Color coded and sorted by project


이 옵션을 사용하면, 기존의 수평이 아닌, 수직으로 탭을 나열해서 보여줍니다. 이렇게 하면 훨씬 많은 탭을 한번에 볼 수 있다는 장점이 있습니다 :)

5. Option 4 : Dynamic Tab Well: Removes tabs based on usage

이 옵션은 좀 똑똑한 옵션인데요. 탭이 많아서 한번에 표시하지 못할 경우, 가장 적게 사용한 탭 순으로 먼저 사라지게 하는 것입니다. 쫌 괜찮죠 :)


- 마치면서

오늘은 Power Tools의 Solution Navigator와 Tab에 대해서 알아봤습니다. 이 것만 해도 상당히 유용한 기능인데요, 소개해드릴 기능이 조금 더 남았습니다. 그럼, 다음에 또 뵙죠 :)

저작자 표시 비영리 변경 금지
신고

Visual Studio 31 (3) - Temp Project

Visual Studio 2010 2010.11.18 09:00 Posted by 뎡바1

개발하다 보면, 지금 작성하고 있는 알고리즘이 내가 원하는 대로 작동하는지 헷갈리기 시작합니다. 하지만, 프로젝트의 일부로 속하는 이 알고리즘이 제대로 작동하는지 알 수 있는 방법은 유닛테스트 외에는 직접 실행하는 방법외에는 없습니다. 알고리즘이 UI나 데이터베이스 같은 다른 부분과 긴밀하게 연계되어 있기 때문에 알고리즘만 따로 테스트할 방법이 마땅히 없으며, UI정도라도 배제하고 테스트하는 방법이 바로 유닛테스트를 활용하는 방법인거죠.

그래서 간단한 예제 데이터를 가지고 확인하고 싶을 때는 콘솔 어플리케이션 프로젝트를 활용하기도 합니다. 알고리즘을 제외한 모든 걸 최고로 단순화 시켜놓고 알고리즘 자체만 가지고 테스트를 진행할 수 있기 때문이죠. 그런데, 이 콘솔 어플리케이션을 애용하다보면, 다음과 같은 상황이 발생합니다.


'ConsoleApplication'뒤에 붙는 숫자가 계속해서 늘어만 가고, 프로젝트 폴더는 점점 더 지저분 해지는 것이죠. 이런 상황을 미연에 방지할 수 있는 기능이 Visual Studio에 있습니다. 어떤 기능인지 한번 알아보시죠.


- 왔다 가는 인생 흔적을 남기지 않도록.

'도구'메뉴에서 '옵션'을 선택하면, 다음과 같은 '옵션'창이 나타납니다.


그리고 '환경'탭을 시작으로 아래쪽으로 많은 옵션이 있죠. 평생 저런 옵션 다 만지는 일이 있을까 싶을 정도로 말이죠. 하지만... '환경'에서 한 칸만 위로 올라가보면...?



'프로젝트 및 솔루션'이라는 탭이 숨어있습니다!! 바로 이 탭이 오늘 소개하려는 기능과 관련이 있습니다. 항목을 찬찬히 살펴보다 보면, '만들어질 때 새 프로젝트 저장'이라는 항목이 있습니다. 이 항목이 기본으로 체크되어 있는데요, 이 항목의 체크를 해제하고 확인을 누릅니다.

그리고 새프로젝트 대화상자를 열어보면,


음... 뭐가 바뀐 걸까요? 한번 이 그림을 가지고 묵상을 해보시기 바랍니다...

답을 알아내셨나요? 힌트를 드리자면, 아까 분명히 탐색기를 보여드렸을 때, 'ConsoleApplication9'까지 있었는데, 새프로젝트 대화상자에 나타난 것은 'ConsoleApplication10'이 아니라 'ConsoleApplication1'입니다.

네, 바로 임시 프로젝트 공간에 프로젝트를 생성하기 때문입니다. 그래서 대화상자를 보시면, 경로를 명시하는 부분이 사라진 것을 확인하실 수 있습니다. 그럼 프로젝트를 생성하겠습니다.

그럼 이제 제가 거짓말 하는 것이 아님을 보여드릴 차례네요. 다음과 같이 프로젝트에 오른쪽 버튼을 클릭하고 '윈도우 탐색기에서 폴더 열기'를 선택합니다.


그러면, 프로젝트 폴더가 탐색기에서 열립니다. 탐색기의 경로를 유심히 보시죠.


Local밑의 Temporary Projects라는 경로가 보이실 겁니다. 바로 여기에 생성되었다가, 솔루션을 닫을 때 저장하지 않으면 사라지는 것이죠. 그럼 솔루션을 닫아볼까요?


저장할 것인지, 버릴 것인지 물어봅니다. 버리면 그냥 사라지는 거죠. 모든 기능이 그렇 듯이 임시 프로젝트에도 몇가지 제약 사항이 있는데요. 첫 번째는, 단일 프로젝트만 가능하다는 것입니다. 임시 프로젝트에 새 프로젝트를 추가하려고 하면 다음과 같은 메세지를 볼 수 있습니다.


그리고 두 번째로, 경로가 필요한 웹 어플리케이션 같은 프로젝트 타입에는 사용할 수 없다는 것입니다.


- 마무리

임시 프로젝트 기능을 통해서 프로젝트 폴더를 깔끔하게 유지하시기 바랍니다. 그럼 다음에 또 뵙죠~ :)

저작자 표시 비영리 변경 금지
신고

Visual Studio 31 (2) - Startpage

Visual Studio 2010 2010.11.15 09:00 Posted by 뎡바1

Visual Studio 31~! 오늘은 그 두번째 시간으로 비주얼 스튜디오를 켜면 항상 보게 되는 시작 페이지에 대해서 이야기 해보겠습니다.


- 기존 작업목록을 관리하기 시작~!

우선 '최근에 사용한 프로젝트' 목록부터 살펴보죠.


비주얼 스튜디오 2008까지는 '최근에 사용한 프로젝트'목록을 편집할수가 없었습니다. 그래서 아무리 자주 작업하는 프로젝트라고 하더라도 잠깐 다른 작업하면서 프로젝트 열고, 생성하다보면 목록에서 사라지는 때가 있습니다. 그럴때는 다시 경로 찾아가서 열어야 했죠. 이제 비주얼 스튜디오 2010에서는 자주 작업하는 프로젝트나 중요한 프로젝트는 목록에 고정시켜놓을 수 있습니다. 항목의 왼쪽에 핀이 있는데 그걸 꾹~ 눌러주면, 프로젝트가 아무리 많이 생성되고 열리더라도 항상 그자리를 지키게 됩니다. :)


그리고 조금 더 편리한 기능도 추가되었는데요, 항목에 마우스 오른쪽 버튼을 눌러보면, 프로젝트의 폴더를 열거나, 목록에서 제거하는 것도 가능합니다. 기존 버전에서는 지원되지 않았었죠.


그리고 시작 페이지에 추가된 또 하나의 작은 기능. 프로젝트를 로드한 뒤에 페이지를 닫을 것인지, 시작할 때 페이지를 표시할 것인지를 선택할 수 있게 되었습니다.


- 시작 페이지를 내 맘대로 설정하기 시작~!


'도구'메뉴에서 '옵션'항목을 선택해서 '옵션'창을 띄우면, '환경'탭 안에, '시작'항목이 있습니다. 여기에 보면, 비주얼 스튜디오를 시작할 때, 시작 페이지를 어떻게 할 것인지에 대한 옵션이 있습니다. 마지막으로 작업했던 솔루션을 로드하게 할 것인지, '새 프로젝트'대화 상자를 띄울 것인지, 등등등. 이 옵션은 기존에도 있었습니다. :) 위 그림에서 비주얼 스튜디오 2010부분을 유심히 보시면 '시작 페이지 사용자 지정'이라는 항목이 있습니다. 시작 페이지도 마음대로 바꿀 수 된거죠! 그럼 다른 사람들이 만든 시작 페이지를 받아서 세팅해보겠습니다.

'도구'메뉴를 통해서 '확장관리자'를 띄우면, 온라인에 다른 사람들이 만들어서 공개해놓은 확장을 검색하고 설치할 수 있습니다. 검색란에 'startpage'라고 입력해보면,


위와 같이 몇가지 시작페이지가 검색됩니다. 여기서 'ItaStartPage'를 한번 설치해보겠습니다. '다운로드'버튼을 누르고,


이어서 나오는 창에서 '설치'를 눌러서 확장을 설치합니다. 그리고 비주얼 스튜디오를 재시작해서 확장이 검색되도록 합니다.


그리고 다시 시작페이지 설정으로 들어와서 '시작 페이지 사용자 지정'항목을 보면, 아까 설치했던 확장이 검색되는 것을 볼 수 있습니다. 확장을 선택하고 '확인'을 눌러서 설정을 적용하면,


아하~! 이 확장 시작페이지는 시작 페이지에 배경 이미지를 설정할 수 있군요 :) 참고로 'ItaStartPage'를 만든 분이 'ItaBackgroundImage'라는 것도 만드셨는데, 설치해보니...


이렇게 코드 편집기 창에도 배경이미지를 설정할 수 있더군요! :)


- 다음 시간에~

그동안 아무 생각없이 매일 대면하던 시작 페이지도 이런저런 기능 변화를 통해서 편리하게, 그리고 내 입맛에 맞게 변경할 수 있게 변했네요. 그럼 다음 시간에 또 뵙죠 :)

저작자 표시 비영리 변경 금지
신고


안녕하세요~ 워너비입니다. 이번에 새로운 시리즈를 하나 시작하게 되었습니다. 이 시리즈는 이름하여 'Visual Studio 31'! 골라먹는 재미가 있는 아이스크림처럼, 비주얼 스튜디오 2010도 엄청나게 다양한 기능 속에서 필요한 기능을 골라쓰는 재미가 있습니다. 몰라서, 어려워서 못 썼던 기능이 있으시다면, 이 시리즈를 통해서 좀 더 친숙해지셨으면 하는게 이 시리즈의 목표입니다. 목표가 달성될지는 살짝 의문이네요 :)


- 오늘은 그 첫 시간.

자, 오늘은 Visual Studio 31의 첫 번째 시간으로 인텔리센스에 대해서 알아보겠습니다. 인텔리센스는 비주얼 스튜디오의 얼굴이라고도 할 수 있죠. 모든 개발자가 가장 많이 활용하며, 코딩에서 가장 편리함을 제공하는 기능이기 때문이죠. 이 인텔리 센스가 비주얼 스튜디오 2010에서 어떻게 달라졌을까요? 비주얼 스튜디오 2008의 인텔리센스와 Before/After를 비교해보도록 하죠 :)


- 관련있는 것들만 보여주는 쎈쓰!



차이점을 발견하셨나요? VS2008에서는 'Console'을 입력하면, 전체목록에서 'Console'과 정확히 일치하는 곳에 포커스를 둡니다. 그런데 포커스를 두기만 할 뿐, 아무런 것도 도와주지 않았습니다. 그런데 VS2010에서는 'Console'을 입력하면, 'Console'이 포함되는 것들만 추려서 인텔리센스에 보여줍니다. 인텔리센스가 많이 똑똑해졌죠? 이제 사용자에게 필요한 정보만 최대한 추려서 보여주는 거죠. 자, 그럼 인텔리센스의 쎈쓰! 가 여기까지 일까요?


- 이름의 일부로도 찾아주는 쎈쓰!


이번에는 어떤 차이점이 있을까요? 기존에는 정확하게 찾으려는 항목과 입력하는 항목의 시작이 같아야만 인텔리센스에서 해당항목을 찾아줬습니다. 그런데 저 처럼 기억력이 저주받은 사람들은 참 슬픈 코딩을 해야했죠. 다른 방법이 있을지도 모르겠지만, 초짜인 제가 했던 방법은 MSDN에 들어가서 얼추비슷한 키워드로 검색을 해서 찾는 방법이었습니다. 그런데 이제 VS2010은 저 같은 초짜&저주받은 기억력 세트를 가진 사람들을 위해서 사용자가 입력하는 문자를 포함하는 항목을 모두 보여주도록 향상되었습니다. 'Color'만 입력하더라도 VS2010은 'Color'가 포함되어있는 'ConsoleColor'를 찾아서 보여주는 거죠 :) 점점 마음에 듭니다.


- 파스칼 케이스로도 찾아주는 쎈쓰!

파스칼 케이스는 뭘까요? 일종의 네이밍 규칙인데요, 서로 다른 단어를 조합해서 메서드 이름을 만들거나 할때, 각 단어의 맨앞글자를 대문자로 표기하는 방법입니다. Console과 Color를 조합해서 'ConsoleColor'를 만드는 것 처럼 말이죠.


기존에는 위에서 설명드렸듯이 시작부터 정확하게 같아야지만 인텔리센스에서 항목을 찾을 수 있었습니다. 하지만 똑똑해진 VS2010에서는 파스칼 케이스의 각 대문자만 입력해도 찾아준다는 거죠. 'CC'를 입력했다면, 'CC'가 직접 포함되는 항목부터, 'C'가 파스칼 케이스로 연속 두번 포함되는 항목도 모두 찾아서 보여줍니다. 이름이 긴 항목을 자주 찾는다면 매우 편리하겠죠 :)


- 없는 클래스도 보여주는 쎈쓰!

TDD를 하려고 할때나, 먼저 코드의 윤곽을 짜놓고 필요한 클래스를 생성하는 식의 프로그래밍을 할때는 존재하지 않는 타입의 객체를 생성해서 사용하게 됩니다. 이런 방식은 어느정도 장점도 가지고 있는데요, 우선 생성하고 하는 클래스의 구체적인 부분에 대해서 생각하는 대신에, 지금 짜려고 하는 코드의 흐름에 우선적으로 집중할 수 있기 때문입니다. 코드의 흐름에 집중하다가, 클래스를 정의하려고 하면 집중의 전환이 일어나기 때문에 그만큼 비효율적인 작업이 될 수도 있습니다.


존재하지 않는 App라는 클래스의 객체를 생성하려고 하면, 기존 버전에서는 전혀 인텔리센스의 지원을 받을 수 없었습니다. 그래서 일일이 쌩코딩을 해야 했었죠. 하지만, VS2010이 출동한다면? 존재하지 않는 타입이라고 하더라도 일단 new를 만나면 존재하는 타입인 것 처럼 인텔리센스에서 보여줍니다. 많이 편리해졌죠~? :)


- 개발자의 취향에 따라 맞춰가는 쎈쓰!



위 Before/After는 모두 VS2010의 캡쳐입니다. 무슨 차이점이 있을까요? Before에서는 입력하는 것과 일치하는 항목에 강조가 되어있고, After에서는 맨위에 별도의 칸이 한칸 추가되어 있으며, 인텔리센스에 강조가 약하게 되어있다는 점이 차이점입니다. After에서 볼 수 있는 것이 VS2010의 인텔리센스에서 제공하는 '서제스천 모드'를 사용한 것입니다. 바로 검색 사이트의 검색창을 떠올리시면 됩니다. 거기서 검색어를 입력하면, 입력하는 검색어와 가장 비슷한 항목을 보여주지만 그 항목이 검색어를 입력하는데 아무런 영향을 주지는 않습니다. 선택하지 않으면 그만이라는 거죠. 그러면, 둘은 어떤 차이가 있을 까요? 기본적인 인텔리센스와 서체스천 모드를 사용한 인텔리센스에서 'App'를 입력하고 Space키를 눌러보면 그 결과를 확인할 수 있습니다.

App라는 클래스는 존재하지 않기 때문에, 기본 모드에서는 가장 비슷한 AppDomain을 선택해버립니다. 하지만, 서제스천 모드에서는 추천항목을 보여줄 뿐, 사용자의 입력에 관여하지 않기 때문에 그냥 App그대로 남은 걸 보실 수 있습니다. 서제스천 모드를 사용하려면, 코드 편집창에서 'Ctrl + Alt + Space'를 누르면 됩니다. 그리고 서제스천 모드에서 기본 모드처럼 추천 항목을 입력하고 싶으면 'Tab'키를 누르면 됩니다 :)


- See you next time :)

오늘은 첫 시작으로 인텔리센스에 대해서 알아봤습니다. 앞으로도 계속해서 골라먹을 수 있는 비주얼 스튜디오 2010의 기능에 대해서 소개해 드릴예정이오니~, 기대해주시기 바랍니다. :)
저작자 표시 비영리 변경 금지
신고