'비동기'에 해당되는 글 2건

Programming/.NET

속도가 느린 입출력장치와의 통신이나 혹은 다른 메서드를 호출했을때 처리가 완료되기를 기다리지 않고 현재 스레드에서 곧장 제어를 넘겨받아 작업을 계속진행하는 방식을 비동기 호출(asynchronous call)이라고 합니다.

 

class Program
{
    delegate void callMethod();

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(null, null);

        Console.WriteLine("현재 스레드 완료");

        cm.EndInvoke(ar);

        Console.Read();
    }

    static void myMethod()
    {
        Console.WriteLine("메서드 실행");

        Thread.Sleep(10000);

        Console.WriteLine("완료");
    }
}

 

위 예제는 메서드의 실행을 호출하되 비동기로 호출하는 방식을 보여주고 있습니다. 우선 메서드의 형식에 대한 델리게이트(delegate)를 선언하고 해당 델리게이트의 BeginInvoke로 메서드를 호출합니다.

 

EndInvoke는 메서드에서 반환값을 받는 경우에 필요하지만 그렇지 않다면 생략이 가능합니다. 다만 반환값이 없더라도 호출해줄것을 권장합니다.

 

BeginInvoke가 호출되면 CPU는 스레드풀에서 대기중인 스레드를 불러와 메서드의 실행을 맡김으로서 비동기를 실현합니다. 그리고 메서드가 종료되면 해당 스레드는 스레드의 상태를 나타내느 상태값을 Signal로 바꾸게 되는데 BeginInvoke에서 반환하는 IAsyncResult타입의 AsyncWaitHandle속성이 스레드의 상태를 대기하는 EventWaitHandle과 같으므로 필요시 이 속성을 사용해 스레드의 실행을 동기화할 수 있습니다.

 

static void Main(string[] args)
{
    callMethod cm = new callMethod(myMethod);
    IAsyncResult ar = cm.BeginInvoke(null, null);
    ar.AsyncWaitHandle.WaitOne();

    Console.WriteLine("현재 스레드 완료");

    cm.EndInvoke(ar);

    Console.Read();
}

 

위와 같이 메서드를 비동기로 호출할때 만약 메서드에 특정 값을 매개변수로 전달해야 한다면 다음과 같이 처리할 수 있습니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(10, 20, null, null);

        Console.WriteLine("현재 스레드 완료");

        int sum = cm.EndInvoke(ar);

        Console.WriteLine("결과 : " + sum);
        Console.Read();
    }

    static int myMethod(int i, int j)
    {
        Thread.Sleep(10000);

        return i + j;
    }
}

 

myMethod에서는 2개의 정수값을 받아 이를 합한 결과를 반환하도록 하였습니다. 그에 따라 델리게이트(delegate)도 같은 형식으로 수정하고 BeginInvoke호출시 필요한 매개변수를 추가해줍니다. 결과값은 이전에 말씀드린대로 EndInvoke를 통해 받을 수 있습니다.

 

비동기 메서드 호출이후 별도의 메서드를 호출하는 콜백메서드가 필요하다면 원하는 메서드를 정의하고 BeginInvoke의 세번째 매개변수에 해당 메서드를 지정해주기만 하면 됩니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(10, 20, methodComplete, null);

        Console.WriteLine("현재 스레드 완료");

        int sum = cm.EndInvoke(ar);

        Console.WriteLine("결과 : " + sum);
        Console.Read();
    }

    static int myMethod(int i, int j)
    {
        Thread.Sleep(10000);

        return i + j;
    }

    static void methodComplete(IAsyncResult ar)
    {
        Console.WriteLine("완료");
    }
}

 

지금까지는 임의의 사용자 메서드를 어떻게 비동기로 호출하는지를 알아봤는데 .NET의 몇몇클래스에도 비동기로 호출할 수 있는 장치가 마련되어 있습니다. 예를 들어 파일을 스트림(Stream)으로 읽어들이는 FileStream클래스의 경우

 

static void Main(string[] args)
{
    using (FileStream fs = new FileStream("C:\\sampe.txt", FileMode.Open, FileAccess.Read)) {
        byte[] b = new byte[fs.Length];
        fs.Read(b, 0, b.Length);

        string s = Encoding.UTF8.GetString(b);
    }

    Console.Read();
}

 

일반적으로 위와 같이 사용할 수 있지만 파일이 너무 크거나 하는등의 이유로 파일의 내용을 다 읽을때까지 대기할 수 없다면 아래와 같이 비동기로의 호출이 가능합니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        FileStream fs = new FileStream("C:\\sample.txt", FileMode.Open, FileAccess.Read);
        byte[] b = new byte[fs.Length];
        fs.BeginRead(b, 0, b.Length, readComplete, b);

        Console.WriteLine("주 스레드 완료");
        Console.Read();
    }
    static void readComplete(IAsyncResult ar)
    {
        byte[] b = (byte[])ar.AsyncState;
        string s = Encoding.UTF8.GetString(b);
        Console.WriteLine(s);
    }
}

 

대부분의 클래스에서 비동기를 지원하는 경우 메서드는 Begin-- 으로 시작합니다. FileStream도 마찬가지 인데 Read메서드의 비동기 호출이 BeginRead이며 BeginRead 메서드를 호출할때 동작 완료시 사용될 메서드를 지정합니다.

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

[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
0 0
Programming/.NET
ADO.NET 의 비동기 처리 방법에는 대략적으로 아래와 같은 방법이 존재합니다.

 IAsyncResult 폴딩

이 방법은 비동기 처리를 시작한 이후 처리완료를 전달받기 위해 IAsyncResult 객체를 통해 폴링을 시도하는 방법입니다. 아래 코드를 참고해 주세요.
SqlConnection DBCon = new SqlConnection();
DBCon.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
            
SqlCommand Command = new SqlCommand();
Command.CommandText = "Select Top 100 * From [Person].[PersonPhone];";
Command.CommandType = CommandType.Text;
Command.Connection = DBCon;
 
DBCon.Open();
 
IAsyncResult ASyncResult = Command.BeginExecuteReader();                
            
while (!ASyncResult.IsCompleted) {
    System.Threading.Thread.Sleep(10000);
}
 
SqlDataReader PhoneReader = Command.EndExecuteReader(ASyncResult);
 
GridView1.DataSource = PhoneReader;
GridView1.DataBind();                
            
DBCon.Close();
위 코드는 PersonPhone 테이블로 부터 상위 100개의 데이터를 가져오는 기능을 수행하는데 SqlCommand 의 객체인 Command 의 BeginExcuteReader() 메소드가 수행되면 프로그램에서 비동기 처리가 시작됩니다. 이 후 처리가 완료될때가지 While 문을 통해 대기하게 되는데 이때 사용된 IAsyncResult 객체의 IsCompleted 속성으로 완료여부를 확인하게 됩니다.
(참고 : 비동기 동작이 제대로 확인되지 않으면 연결문자열에서 Asynchronous Processing=True 구문이 추가되어 있는지 확인해야 합니다.)

참고로 SqlConnection 클래스에는 State 속성을 제공하고 있는데 이 값에 따라 현재 데이터 DB 와의 연결상태를 파악할 수 있습니다.
 
 대기핸들과의 연동

비동기 처리가 시작되고 전체 혹은 일부의 처리가 완료될때 까지 대기하는 대기핸들과의 연동을 시도하는 방법으로서 비동기 처리 방법중 가장 권장되고 있는 방법이기도 합니다. 또한 이 방법은 다중 비동기 처리와 함께 전체 혹은 일부 처리가 동작을 끝낼때 까지 대기할 수 있도록 프로그램을 작성할 수 있을 뿐만 아니라 더 나아가 서로 의존하는 프로세스들을 대기시키고 그외 다른 프로세스는 별도로 처리하는 구현도 가능하게 합니다.

아래 코드는 WaitHandle 클래스의 WaitOne 메소드가 사용된 예제입니다. 이 메소드가 호출되면 비동기 프로세스가 동작을 끝낼때 까지 대기하게 되고 동작을 완료하면(true) 원하는 처리를 수행할 수 있도록 코드를 작성할 수 있습니다..
SqlConnection DBCon = new SqlConnection();
DBCon.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
            
SqlCommand Command = new SqlCommand();
Command.CommandText = "Select Top 100 * From [Person].[PersonPhone];";
Command.CommandType = CommandType.Text;
Command.Connection = DBCon;
 
DBCon.Open();
 
IAsyncResult ASyncResult = Command.BeginExecuteReader();
System.Threading.WaitHandle WHandle = ASyncResult.AsyncWaitHandle;
 
if (WHandle.WaitOne() == true) {
    SqlDataReader PhoneReader = Command.EndExecuteReader(ASyncResult);
    GridView1.DataSource = PhoneReader;
    GridView1.DataBind();
    DBCon.Close();
}
else {
    
}
else 이하 부분은 비동기 프로세스에 대해 타임아웃등의 문제로 인하여 동작을 제대로 수행하지 못한 경우로서 예외사항을 따로 작성해 두면 됩니다.

단일이 아닌 다중 대기핸들을 사용하는 경우도 크게 다르지 않습니다. 다중 핸들에서 적용할 Command, DataReader 등 쿼리 수행에 필요한 객체등을 각각따로 생성하고 그 만큼 이들 객체에 적용할 대기핸들을 생성해 사용하면 됩니다.
SqlConnection DBCon = new SqlConnection();
DBCon.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
 
SqlCommand Command1 = new SqlCommand();
Command1.CommandText = "Select Top 100 * From [Person].[PersonPhone];";
Command1.CommandType = CommandType.Text;
Command1.Connection = DBCon;
 
SqlCommand Command2 = new SqlCommand();
Command2.CommandText = "Select Top 10 * From [Person].[PersonPhone];";
Command2.CommandType = CommandType.Text;
Command2.Connection = DBCon;
 
DBCon.Open();
 
IAsyncResult ASyncResult1 = Command1.BeginExecuteReader();
IAsyncResult ASyncResult2 = Command2.BeginExecuteReader();
 
System.Threading.WaitHandle[] WHandles = new System.Threading.WaitHandle[2];
 
System.Threading.WaitHandle top100WHandle = ASyncResult1.AsyncWaitHandle;
System.Threading.WaitHandle top10WHandle = ASyncResult2.AsyncWaitHandle;
 
WHandles[0] = top100WHandle;
WHandles[1] = top10WHandle;
 
System.Threading.WaitHandle.WaitAll(WHandles);
 
SqlDataReader phoneReader1 = Command1.EndExecuteReader(ASyncResult1);
SqlDataReader phoneReader2 = Command2.EndExecuteReader(ASyncResult2);
 
//회수된 데이터의 처리코드
 
DBCon.Close();
다만 데이터베이스에 연결하는 객체는 하나를 사용하는데 이럴 경우 연결문자열에 Multiple Active Result Set (MARS) 을 지원할 수 있도록 MultipleActiveResultSets=True 구문을 추가해야 합니다.

다중 대기핸들을 사용하는 경우 위에서 쓰인 WaitAll 메소드 보다는 WaitAny 메소드를 사용하는 경우가 많은데 WaitAny 메소드는 각각의 비동기 처리가 끝나면 다른 프로세스가 끝날때 까지 대기하는것 없이 바로 해당 프로세스의 처리 결과를 다룰 수 있도록 합니다. 반면 WaitAll 메소드는 배열에 있는 모든 프로세스가 완료되어야 결과를 반환하는 형태의 동작을 수행합니다.
SqlConnection DBCon1 = new SqlConnection();
DBCon1.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
 
SqlConnection DBCon2 = new SqlConnection();
DBCon2.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
 
SqlCommand Command1 = new SqlCommand();
Command1.CommandText = "Select Top 100 * From [Person].[PersonPhone];";
Command1.CommandType = CommandType.Text;
Command1.Connection = DBCon1;
 
SqlCommand Command2 = new SqlCommand();
Command2.CommandText = "Select Top 10 * From [Person].[PersonPhone];";
Command2.CommandType = CommandType.Text;
Command2.Connection = DBCon2;
 
DBCon1.Open();
DBCon2.Open();
 
IAsyncResult ASyncResult1 = Command1.BeginExecuteReader();
IAsyncResult ASyncResult2 = Command2.BeginExecuteReader();
 
System.Threading.WaitHandle top100WHandle = ASyncResult1.AsyncWaitHandle;
System.Threading.WaitHandle top10WHandle = ASyncResult2.AsyncWaitHandle;
 
System.Threading.WaitHandle[] Handles = new System.Threading.WaitHandle[] { top100WHandle, top10WHandle };
 
for (int index = 0; index < 2; index++) {
    switch (System.Threading.WaitHandle.WaitAny(Handles)) {
        case 0:
            SqlDataReader phoneReader1 = Command1.EndExecuteReader(ASyncResult1);
            GridView1.DataSource = phoneReader1;
            GridView1.DataBind();
            DBCon1.Close();
            break;
        case 1:
            SqlDataReader phoneReader2 = Command2.EndExecuteReader(ASyncResult2);
            GridView2.DataSource = phoneReader2;
            GridView2.DataBind();
            DBCon2.Close();
            break;
    }
}
위 코드는 WaitAll 대신 WaitAny 메소드를 사용하도록 수정된 예제입니다. WaitAny 메소드는 결과로 정수형의 값을 반환하는데 이 값은 위에서 지정한 대기핸들에 대한 배열의 Index 번호에 해당합니다. 즉, WaitAny는 특정 프로세스가 처리를 완료하면 해당 프로세스에 대한 배열의 Index 값을 반환하고 곧 이 값을 확인하면 현재 완료된 프로세스를 알 수 있게 되는 것입니다.

 CallBack 사용

비동기 프로세스를 시작하는 동안 콜백(callback) 메소드를 사용하는 방법으로서 다른 작업을 병렬적으로 수행할 수 있도록 하며 비동기 프로세스가 종료될때 프로세스를 정리하고 비동기 처리가 종료되면 특정 메소드를 불러 들일 수 있도록 callback 메소드를 호출합니다. 아래 코드를 참고해 주세요.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Webtest.Default" Async="true" %>
우선 위와 같이 페이지에 Async="true" 항목을 추가설정합니다.
BeginEventHandler _begin = new BeginEventHandler(this._Begin);
EndEventHandler _end = new EndEventHandler(this._End);
AddOnPreRenderCompleteAsync(_begin, _end);
그런다음 비동기 프로세스를 시작하는 메소드(_Begin)와 끝난뒤 호출될 CallBack 메소드(_End)에 대한 이벤트 핸들러를 등록하고 AddOnPreRenderCompleteAsync 메소드를 사용해 Page 의 PreRender 이벤트 단계에서 메소드가 모두 처리될 수 있도록 동기화합니다. 이러한 작업이 필요한 이유는 비동기 특성상 Page Load 가 모두 이루어지고 난 이후에 실제 처리가 완료될 수 있는데 Page 자체에 결과에 대한 표시를 하려는 경우 제대로된 결과를 볼 수 없기 때문입니다.
IAsyncResult _Begin(object sender, EventArgs e, AsyncCallback callback, object state)
{
    SqlConnection DBCon = new SqlConnection();
    DBCon.ConnectionString = ConfigurationManager.ConnectionStrings["new_connection"].ConnectionString;
 
    Command = new SqlCommand();
    Command.CommandText = "Select Top 10 * From [Person].[PersonPhone];";
    Command.CommandType = CommandType.Text;
    Command.Connection = DBCon;
 
    DBCon.Open();
 
    AsyncCallback AsyncResult = new AsyncCallback(_End);
 
    return Command.BeginExecuteReader(callback, state, CommandBehavior.CloseConnection);
}
이제 비동기 처리가 시작될 _Begin 메소드를 위와 같이 작성합니다. 비동기 처리는 BeginExecuteReader 메소드를 호출하면서 시작되며 메소드의 동작이 완료되면 콜백으로 처리가 넘어가게 됩니다.
public void _End(IAsyncResult ar)
{
    SqlDataReader PhoneReader = Command.EndExecuteReader(ar);
 
    GridView1.DataSource = PhoneReader;
    GridView1.DataBind();
}
CallBack 메소드입니다. 메소드에 보이는 Command 객체는 _Begin 메소드에서 사용된 것과 일치하므로 Command 객체는 전역변수로서 선언하도록 합니다.

SqlCommand Command;
 비동기화 처리 취소

비동기처리는 프로그램의 실행흐름과 데이터처리 사이에 동기화를 하지 않고 프로그램실행을 계속 진행해 나갈 수 있다는 장점이 있지만 비동기처리가 예상외로 오래 걸리는 경우 여러가지 문제가 발생할 수 있습니다. 이러한 문제를 해결하기 위해서는 프로그램 최종 사용자나 타임아웃시 Command 객체의 Cancel() 메소드를 호출함으로서 비동기 처리자체를 취소하도록 해야 합니다.

Cancel() 메소드는 어떠한 값도 반환하지 않고 비동기처리 동작을 취소하지만 이미 Command 객체에 의해 실행된 query 동작은 되돌릴 수 없습니다. 때문에 롤백(roll back)과정이 필요한 작업이라면 query 실행전 transaction 을 사용하는등의 조치가 있어야 합니다.

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

[ASP.NET] 사용자 정의 컨트롤  (0) 2016.07.21
[ASP.NET] 가장 (impersonation)  (0) 2016.07.12
[ASP.NET] ADO.NET 의 비동기 처리  (0) 2016.07.05
[C#] FtpWebRequest / FtpWebResponse  (0) 2016.06.29
[ASP.NET] SqlDataAdapter  (0) 2016.06.14
[C#] FileInfo  (1) 2016.06.09
0 0
1
블로그 이미지

클리엘