블로그 이미지
생각처럼

카테고리

전체보기 (209)
TOOL (1)
다이어리 (1)
Bit (200)
HELP? (0)
Total
Today
Yesterday

달력

« » 2025.2
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28

공지사항

태그목록

최근에 올라온 글

비동기

Bit / 2012. 2. 3. 17:40

무명(anonymous) 메소드를 통해서 좀더 간편하게 비동기 프로그램을 작성할 수 있지만,
하지만 아직도 IEnumerator 멤버 변수를 계속 선언해야 하고, 거의 비슷한 무명 메소드들을 반복해서 작성해야 하는 번거로움은 남아 있었다.
이것도 줄일 수 없는지 고민해 봤다.


다음 코드를 만들어 봤다.

    public class IteratorRunner
    {
        private static LocalDataStoreSlot ITERATOR_DATA = Thread.AllocateDataSlot();

 

        public static void StartIterator( IEnumerator iterator )
        {
            IteratorData data = new IteratorData( iterator, SynchronizationContext.Current );

 

            Thread.SetData( ITERATOR_DATA, data );
            iterator.MoveNext();
        }

 

        public static AsyncCallback ResumeIterator()
        {
            IteratorData data = (IteratorData)Thread.GetData( ITERATOR_DATA );

 

            return new AsyncCallback(
                delegate( IAsyncResult state )
                {
                    data.context.Post(
                        delegate( object s )
                        {
                            Thread.SetData( ITERATOR_DATA, data );
                            data.enumerator.MoveNext();
                        },
                        null );
                } );
        }

       

        //단순히 두가지 정보를 담기 위한 클래스
        private class IteratorData
        {
            public IEnumerator enumerator;
            public SynchronizationContext context;


            public IteratorData( IEnumerator enumerator, SynchronizationContext context )
            {
                this.enumerator= enumerator;
                this.context = context;
            }
        }

    }

위의 IteratorRunner를 이용하여 비동기 프로그램을 작성해 보면 다음과 같이 훨씬 간단해 진다.

class Form1 : Form
{

    ...


    private void button1_Click( object sender, EventArgs e )
    {           

            IteratorRunner.StartIterator( GetData() );
    }

 

    private IEnumerator<int> GetData()

    {

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create( "http://www.naver.com" );
        request.Method = "GET";

        

        IAsyncResult ar = request.BeginGetResponseIteratorRunner.ResumeIterator(), null );
        yield return 1;

        

        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse( ar );

 

        MemoryStream recvData = new MemoryStream();
        using( Stream inStream = response.GetResponseStream() )
        {
                byte[] buffer = new byte[1024];
                int read = 0;
                do
                {
                    IAsyncResult ar2 = inStream.BeginRead( buffer, 0, buffer.Length,
                                                                              IteratorRunner.ResumeIterator(), null );
                    yield return 1;

 

                    if( ( read = inStream.EndRead( ar2 ) ) <= 0 )
                        break;

 

                    recvData.Write( buffer, 0, read );
                }
                while( read > 0 );
        }

        response.Close();
        

        String recvStr = Encoding.UTF8.GetString( recvData.ToArray() );
        this.textBox1.Text = recvStr;

    }

 

    ...
}

일단 눈에 뛰는 점은 반복적으로 작성해야 했던 무명 메소드를 작성하지 않았다. 콜백 delegate를 넣어야 하는 부분이 모두 IteratorRunner.ResumeIterator 메소드 호출로 대체되었다. 또한 비동기 작업마다 IEnumerator 멤버를 생성해 주어야 했던 것도 없어졌다.

이쯤되면 쓸만해 진것 같다. 이제 비동기 작업이 필요하면 비동기 작업을 수행하는 메소드(위에서는 GetData) 만 작성하면 된다. 다른 부속물없이 말이다.

 

그럼 위의 코드가 왜 작동하게 되는지 설명해 보자.

이를 위해서는 반복기(Iterator)와 관련하여 그 실행흐름을 좀 살펴볼 필요가 있다.

 

 

위의 그림에서 보면 비동기 작업이 완료되어 무명 메소드 부분이 실행되면 enumerator.MoveNext()를 호출하게 되는데,

이때 이 MoveNext()가 반환할 때까지 실행되는 경로가 파란색으로 표시되어 있다.

 

이 파란색 선은 하나의 Thread가 실행되는 경로다. 따라서 위의 그림은 이전 ResumeIterator 내부의 무명 메소드에서 부터 다음 ResumeIterator 까지, 하나의 Thread가 연속해서 실행한다는 이야기다. 이것은 이전 ResumeIterator 메소드 호출에서의 정보를, 다음 ResumeIterator 호출때 까지 전달할 수 있는 힌트가 된다.

 

이 파란색 선으로 표시된 실행흐름이 하나의 Thread에 의해서만 이루어 지기 때문에 Thread Local 변수를 이용하면 정보를 전달할 수 있다. 하나의 Thread가 파란색 선으로 표시된 실행경로를, ThreadLocal을 이용해 자신만의 데이터를 가지고, 방해없이 한번에 실행시키게 되기 때문에, 여러 Thread에 의해서 IteratorRunner.ResumeIterator()가 호출된다고 하더라도 서로간에 간섭없이 작동하게 된다.

이렇게 되면 초기의 enumerator의 값을 비동기 작업이 진행되는 내내 전달할 수 있기 때문에, IEnumerator 를 멤버 변수로 선언해야 했던 부분도 제거할 수 있게 된다.

또한 이런 구조 덕분에 반복해서 작성해야 했던 무명 메소드도 IteratorRunner.ResumeIterator()를 호출 하는 것으로 대체할 수 있다.

 

이제 진짜로 좀 쓸만해 진거 같다. 

Posted by 생각처럼
, |

최근에 달린 댓글

최근에 받은 트랙백

글 보관함