비동기

비동기는 시간이 걸리는 네트워크 요청, 파일 읽기 등을 수행할 때 코드의 실행을 차단하지 않고 비동기적으로 처리할 수 있게 해주는 중요한 개념이다. 자바스크립트의 비동기와 관련된 기능들은 웹 애플리케이션을 더 빠르고 반응성이 좋게 동작하게 한다.

 

콜백 함수

콜백 함수는 다른 함수의 인수로 전달되는 함수이다.

비동기 작업이 완료되면 호출되며 비동기 작업을 처리하는 가장 기본적인 방법이다.

function fetchData(callback) {
    setTimeout(() => {
        const data = { id: 1, name: 'John Doe' };
        callback(data);
    }, 1000); // 1초 후에 콜백 함수 호출
}

fetchData((data) => {
    console.log('Data received:', data);
});

 

여기서 fetchData 함수는 1초 후에 데이터를 콜백 함수에 전달한다. 이 방식은 간단하지만, 여러 비동기 작업이 중첩될 경우 콜백 지옥이라 불리는 가독성이 떨어지는 코드가 될 위험이 있다.

 

프로미스

프로미스는 비동기 작업의 완료 또는 실패를 처리하는 객체이다. then, catch, finally 메서드를 사용하여 비동기 작업의 결과를 처리한다.

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: 'John Doe' };
            resolve(data); // 성공적으로 데이터를 반환
        }, 1000);
    });
}

fetchData()
    .then((data) => {
        console.log('Data received:', data);
    })
    .catch((error) => {
        console.error('Error:', error);
    });

 

프로미스를 사용하면 코드의 가독성이 개선되고, 여러 비동기 작업을 체인으로 연결하여 처리할 수 있다. 프로미스가 성공적으로 완료될 때 resolve가 호출되며 'value'에는 성공적으로 완료된 후 전달되는 값이다. 실패할 경우 reject가 호출되며 'reason'은 프로미스가 실패한 이유를 나타내는 값이다.

 

프로미스는 대기(pending), 이행(fulfilled), 거부(rejected) 세 가지 상태를 가질 수 있다.

 

대기

프로미스가 생성된 초기 상태로 'then', 'catch', 'finally' 메서드가 아직 호출되지 않은 상태이다.

 

이행

프로미스가 성공적으로 완료된 상태로 'then' 메서드가 호출된다.

 

거부

프로미스가 실패한 상태로 'catch' 메서드가 호출된다.

 

프로미스가 이행, 거부 상관없이 작업이 완료되었을 때 항상 'finally'가 호출된다. 보통 프로미스의 상태에 상관없이 반드시 실행되어야 하는 리소스를 해제, 로딩 상태를 해제하는 등의 작업 같은 코드를 작성할 때 사용된다.

const myPromise = new Promise((resolve, reject) => {
  let success = true; // 작업 성공 여부를 결정하는 변수
  
  setTimeout(() => {
    if (success) {
      resolve("작업이 성공적으로 완료되었습니다!"); // 이행 상태로 전환
    } else {
      reject("작업이 실패했습니다."); // 거부 상태로 전환
    }
  }, 1000); // 1초 후에 상태 결정
});

myPromise
  .then((value) => {
    console.log("이행됨:", value); // 프로미스가 이행되면 실행
  })
  .catch((reason) => {
    console.log("거부됨:", reason); // 프로미스가 거부되면 실행
  })
  .finally(() => {
    console.log("프로미스가 완료됨."); // 프로미스가 완료되면 항상 실행
  });

 

async, await

async와 await 키워드는 ES2017에서 도입된 비동기 프로그래밍의 최신 방법이다.

async 함수는 항상 프로미스를 반환하며, await 키워드는 프로미스가 처리될 때까지 함수의 실행을 일시 중지한다. 이를 통해 비동기 코드를 동기식 코드처럼 작성할 수 있게 된다.

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: 'John Doe' };
            resolve(data); // 성공적으로 데이터를 반환
        }, 1000);
    });
}

async function getData() {
    try {
        const data = await fetchData(); // 프로미스가 처리될 때까지 대기
        console.log('Data received:', data);
    } catch (error) {
        console.error('Error:', error);
    }
}

getData();

 

동기식처럼 처리되는 코드처럼 보이면서도 async, await 키워드를 사용해서 비동기적으로 동작하게 한다.

 

콜백 지옥 (Callback Hell)

콜백 지옥은 자바스크립트에서 비동기 작업을 중첩된 콜백 함수로 처리할 때 발생하는 문제로, 코드의 가독성과 유지보수성이 크게 떨어지는 현상을 말한다.

function authenticateUser(username, password, callback) {
    setTimeout(() => {
        console.log('User authenticated');
        callback(null, { userId: 1, username: username });
    }, 1000);
}

function fetchUserData(userId, callback) {
    setTimeout(() => {
        console.log('User data fetched');
        callback(null, { userId: userId, data: 'Some user data' });
    }, 1000);
}

function processData(data, callback) {
    setTimeout(() => {
        console.log('Data processed');
        callback(null, { processedData: 'Processed data' });
    }, 1000);
}

authenticateUser('user1', 'password123', (authError, authData) => {
    if (authError) {
        console.error('Authentication error:', authError);
        return;
    }
    fetchUserData(authData.userId, (fetchError, userData) => {
        if (fetchError) {
            console.error('Fetch error:', fetchError);
            return;
        }
        processData(userData.data, (processError, processedData) => {
            if (processError) {
                console.error('Processing error:', processError);
                return;
            }
            console.log('All tasks completed successfully:', processedData);
        });
    });
});

 

'authenticateUser' 함수는 사용자를 인증하고 결과를 콜백 함수에 전달한다.

인증이 성공하면 'fetchUserData' 함수가 호출되어 사용자 데이터를 가져온다. 

사용자 데이터가 성공적으로 가져오면, 'processData' 함수가 호출되어 데이터를 처리한다.

 

위 코드는 각 단계별로 성공 여부를 대기하고 성공 시 다음 단계로 넘어가는 구조이다. 이러한 구조를 중첩된 콜백 함수라고 하며 코드가 길어지고 복잡해져 가독성이 떨어진다는 것이 확인된다. 가독성이 떨어지면 유지보수와 에러 처리에도 어려움이 발생할 수밖에 없다.

 

이를 예방하는 방법으로는 위에서 서술된 프로미스 사용, async, await 키워드를 사용하는 방법들이 있고 그 밖에도 몇 가지 방법들이 존재한다.

 

이름이 있는 함수로 콜백 분리

익명 함수 대신 이름이 있는 함수를 사용하여 콜백을 분리하면 코드의 가독성과 재사용성이 향상된다.

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: 'John Doe' };
            resolve(data);
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log('Data received:', data);
        return fetchData(); // 다음 비동기 작업
    })
    .then(data => {
        console.log('Next data received:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

 

모듈화 및 함수 분리

비동기 작업을 여러 작은 함수로 나누고 모듈화 하면 코드가 더 관리하기 쉬워진다.

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: 'John Doe' };
            resolve(data);
        }, 1000);
    });
}

async function handleFirstData() {
    try {
        const data = await fetchData();
        console.log('Data received:', data);
        return data;
    } catch (error) {
        console.error('Error in handleFirstData:', error);
    }
}

async function handleSecondData() {
    try {
        const data = await fetchData();
        console.log('Next data received:', data);
        return data;
    } catch (error) {
        console.error('Error in handleSecondData:', error);
    }
}

async function processAllData() {
    await handleFirstData();
    await handleSecondData();
}

processAllData();

 

비동기 제어 라이브러리 사용

비동기 제어 흐름을 관리하기 위해 'async.js'와 같은 비동기 작업을 관리하는 데 유용한 유틸리티를 제공하는 라이브러리를 사용할 수 있다.

 

async.js 등 라이브러리를 사용하기 위해서는 설치가 필요하다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/async/3.2.0/async.min.js"></script>

 

node.js 환경에서는 npm install async를 사용하여 설치할 수 있다.

npm install async

 

async.js 사용법

시퀀스 작업 처리

'async.series'를 사용하여 비동기 작업을 순차적으로 처리할 수 있다.

const async = require('async');

function fetchData(callback) {
    setTimeout(() => {
        console.log('Data fetched');
        callback(null, { id: 1, name: 'John Doe' });
    }, 1000);
}

function processData(data, callback) {
    setTimeout(() => {
        console.log('Data processed:', data);
        callback(null, { processedData: true });
    }, 1000);
}

async.series([
    function(callback) {
        fetchData(callback);
    },
    function(callback) {
        fetchData((err, data) => {
            if (err) return callback(err);
            processData(data, callback);
        });
    }
], function(err, results) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log('All tasks completed:', results);
    }
});

 

병렬 작업 처리

'async.parallel'을 사용하여 비동기 작업을 병렬로 처리할 수 있다.

const async = require('async');

function fetchData1(callback) {
    setTimeout(() => {
        console.log('Data1 fetched');
        callback(null, { id: 1, name: 'John Doe' });
    }, 1000);
}

function fetchData2(callback) {
    setTimeout(() => {
        console.log('Data2 fetched');
        callback(null, { id: 2, name: 'Jane Doe' });
    }, 500);
}

async.parallel([
    function(callback) {
        fetchData1(callback);
    },
    function(callback) {
        fetchData2(callback);
    }
], function(err, results) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log('All tasks completed in parallel:', results);
    }
});

 

각 작업 처리

'async.each'를 사용하여 배열의 각 요소에 대해 비동기 작업을 수행할 수 있다.

const async = require('async');

const items = [1, 2, 3, 4, 5];

function processItem(item, callback) {
    setTimeout(() => {
        console.log('Processed item:', item);
        callback(null, item);
    }, 1000);
}

async.each(items, processItem, function(err) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log('All items processed');
    }
});

 

728x90
반응형

delegate

C/C++에는 함수를 가리키는 함수 포인터가 있다. 이를 사용하면 함수를 인수로 전달하거나 함수를 반환하는 등 다양한 방식으로 함수를 조작할 수 있는데 C#에서는 포인터를 직접적으로 사용할 수 없다. 그래서 C#에서 함수 포인터를 대신하는 개념으로 메서드를 참조하는 형식의 변수를 선언할 수 있도록 delegate를 사용한다.

 

delegate는 함수 포인터와 비슷한 개념으로 메서드에 대한 참조를 저장하여 메서드를 인수로 전달하거나 메서드를 반환하는 등의 작업을 수행할 수 있다. 메서드 자체를 하나의 변수로 다룰 수 있기 때문에 코드 자체를 참조하는 개념으로 볼 수 있다. 

선언

delegate void DelegateDefault();
delegate int DelegateReturn();
delegate int DelegateReturnParam(int x, int y);

static void Main(string[] args)
{
	DelegateDefault delegateDefault = new DelegateDefault(Print_1);
	DelegateReturn delegateReturn = new DelegateReturn(Print_2);
	DelegateReturnParam delegateReturnParam = new DelegateReturnParam(Print_3);

	delegateDefault();
	int result = delegateReturn();
	int result_2 = delegateReturnParam(1, 2);
}

static void Print_1()
{
	Console.WriteLine("Print_1");
}

static int Print_2()
{
	int num = 2;
	Console.WriteLine("Print_Out" + num);
	return num;
}

static int Print_3(int x, int y)
{
	int sum = x + y;
	Console.WriteLine("Print_Out" + sum);
	return sum;
}

선언된 델리게이트는 동일한 형식으로 선언된 메서드의 참조만 가능하다. 델게이트를 호출하면 참조중인 메서드를 호출하게 된다.

 

delegate chain

또한 델리게이트는 += 연산자를 통해서 동시에 여러 메서드를 참조할 수 있다.

delegate void DelegateChain();
DelegateChain delegateChain;
void Main(string[] args)
{
    delegateChain += Print_1;
    delegateChain += Print_2;
    delegateChain += Print_3;

	// ?.Invoke()를 사용하면 delegateChai이 null인 경우에 에러가 발생하는걸 방지할 수 있다.
    delegateChain?.Invoke();
    
    delegateChain -= Print_3;
    delegateChain -= Print_2;
    delegateChain -= Print_1;
}

static void Print_1() { }
static void Print_2() { }
static void Print_3() { }

참조중인 메서드 중에서 참조를 해제하려면 -= 연산자를 사용하면된다.

 

Anonymouse method call

델리게이트 체인을 활용해서 익명 메서드 호출도 가능하다.

void Main(string[] args)
{
    delegateChain += Print_1;
    delegateChain += Print_2;
    delegateChain += Print_3;
    delegateChain += delegate ()
    {
    	Console.WriteLine();
    };

    delegateChain?.Invoke();
    
	~
}

익명 메서드는 일회성으로 사용될 코드를 간단하게 작성할 때 주로 사용되며 특히 해당 코드가 특별한 용도를 제공하지 않거나 재사용될 필요가 없는 경우에 사용된다.

 

delegate는 대표적으로 이벤트 처리, 비동기 처리, 콜백에 사용된다.

 

Event

프로그래밍에서 이벤트는 어떤 특정한 조건이나 상황이 발생했을 때 이를 감지하고 처리하는 기능을 의미한다.

예를 들어 마우스 클릭, 키보드 입력 등이 이벤트이다. 이벤트 기반 프로그래밍(Event-driven programming)은 이러한 상황에서 반응하듯이 특정한 기능을 수행시키는 이벤트 기반으로 만드는 방식을 말하며 코드와의 상호작용으로 객체 간의 통신을 쉽게하고 객체의 라이프 사이클과 관련된 처리를 간편하게 할 수 있다.

 

Event Subscribe

이벤트 구독

일반적으로 이벤트를 다룰때 이벤트가 발생할때 동작시킬 기능에 해당하는 메서드의 할당과 참조는 외부에서 하더라도 이벤트의 발동은 델리게이트를 선언한 클래스 내에서 하는것이 권장된다.

 

외부에서 delegate에 메서드를 참조시키는걸 Subscribe, 참조한 메서드를 제거하는걸 Unsubscribe 라고 한다.

이렇게 외부에서 메서드를 참조하는 클래스를 Subscriber Class(구독자 클래스), 델리게이트가 선언된 클래스를 Publisher Class(게시자 클래스)라고 부른다.

 

public delegate MyDelegate();
// 게시자 클래스
public class MyPublisher
{
	// 델리게이트
	public MyDelegate MyEvent;
    public void OnEvent()
    {
    	MyEvent?.Invoke();
    }
}

// 구독자 클래스
public class MySubscriber
{
	private Publisher publisher = new Publisher();
	public void Subscirbe()
    {
    	// 구독
    	publisher.MyEvent += SomeMethod; 
    }
    
    public void UnSubscribe()
    {
    	// 해지
 		publicsher.MyEvent -= SomeMethod;
    }
    
    public void SomeMethod(){} 
}

 

event keyword

델리게이트를 이벤트 목적으로 사용할때 event 키워드를 사용한다.

event 키워드를 통해서 delegate가 선언되면 해당 델리게이트는 외부 클래스에서 실행이 불가능하게 된다.

따라서 외부 클래스는 이벤트를 구독과 해지만 가능하며 이벤트의 호출은 선언된 클래스 내부 오직 한곳에서만 일어나기 때문에 안전하게 사용할 수 있게 된다.

 

선언

class EventListner
{
    // 구독할 델리게이트 
    public delegate void EventHandler();
    // event를 통해서 MyEvent는 내부에서만 호출 가능해진다.
    public event EventHandler MyEvent;

    public void OnMyEvent()
    {
        // ? : EventName null check
        EventName?.Invoke();
    }
}

class SubscriberClass
{
	EventLister listner = new EventListenr();
    public void Subscribe()
    {
    	listner.MyEvent += SomeMethod
        // Cannot Call delegate
        lister.MyEvent?.Invoke();
    }
    
    public void SomeMethod(){}
}

 

event 제한자로 선언된 delegate를 외부 클래스에서 직접 호출을 시도하면 에러가 발생한다.

따라서 더 안전하게 이벤트를 구현할 수 있게 해주는 키워드라 할 수 있다.

 

Async

delegate의 비동기 작업은 일반적으로 멀티스레딩을 구현할 때 사용된다. 델리게이트를 통해서 메서드의 실행을 다른 스레드에게 위임하여 해당 스레드에서 비동기적으로 작업을 처리하고, 작업이 완료되면 해당 스레드가 호출한 스레드에게 결과를 알려준다. 이 작업 방식은 보통 결과를 필요로 하지 않는 작업을 빠르고 효율적으로 처리하기 위해서 사용한다.

 

보통 BeginInvoke()와 EndInvoke() 메서드를 활용해서 비동기를 구현한다.

* BeginInvoke : 해당 작업을 다른 스레드에서 비동기적으로 실행

* EndInvoke : 해당 작업이 완료되었는지 확인 후 결과를 반환

 

public delegate int AsyncMethodDelegate(int x, int y);

public class Calculator
{
    public int Add(int x, int y)
    {
        return x + y;
    }

	// Add 메서드를 비동기적으로 실행
    public void AddAsync(int x, int y, AsyncCallback callback)
    {
        AsyncMethodDelegate asyncMethod = new AsyncMethodDelegate(Add);
        // 작업이 종료되면 callback
        asyncMethod.BeginInvoke(x, y, callback, null);
    }
}

public class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        AsyncCallback callback = new AsyncCallback(CalculateCompleted);

        calculator.AddAsync(1, 2, callback);
		
        Console.ReadLine();
    }
	
    // callback 호출 함수
    static void CalculateCompleted(IAsyncResult ar)
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodDelegate asyncMethod = (AsyncMethodDelegate)result.AsyncDelegate;

        int resultValue = asyncMethod.EndInvoke(ar);
        Console.WriteLine("Result: " + resultValue);
    }
}

 

Callback Function

콜백 함수는 다른 함수에서 인자로 전달되어 실행되는 함수를 말한다.

일반적으로 다른 함수의 작업이 완료된 후 호출된다.

 

만약 비동기적으로 실행되는 작업이 있을 때 해당 작업이 완료된 후 특정 동작을 실행시키고 싶다고 할때 여기서 콜백 함수를 사용할 수 있다. 비동기 작업을 수행하는 함수는 작업이 완료되면 콜백 함수를 호출하여 완료된 작업에 대한 처리를 수행하게 된다. 

 

콜백 함수는 함수 포인터(delegate)나 람다식 등의 방식으로 전달될 수 있으며 주로 이벤트 처리나 비동기 처리 등에서 사용된다.

 

EventHandler가 대표적인 콜백 함수라 볼 수 있다.

// 버튼 클릭 이벤트 처리를 위한 콜백 함수
private void Button_Click(object sender, EventArgs e)
{
    // 버튼이 클릭되었을 때 실행될 코드
}

// 이벤트 핸들러 등록을 위한 코드
button1.Click += new EventHandler(Button_Click);

버튼이 클릭되면 호출되는 Button_Click 메서드가 콜백 함수로 button1.Click 이벤트에 등록되어 클릭 이벤트가 발생하면 해당 콜백 함수가 실행되는 구조이다. 이렇게 delegate를 사용하여 콜백 함수를 등록함으로써 이벤트 처리와 같이 비동기적인 작업을 보다 효율적으로 처리할 수 있다.

 

Built-in delegate

C#의 내장형 delegate로 일반화된 형태의 델리게이트를 말한다.

매번 델리게이트를 선언하거나 만들 필요 없이 메서드를 매개변수로 전달하고 반환 값을 받을 수 있도록 하는것이 목적이다.

Action

Action은 C#의 내장 델리게이트 중 하나이다. 반환값이 없는 메서드를 참조할 수 있는 타입으로 매개변수를 받아서 반환값이 없는 메서드를 참조하는 기능을 한다.

Func

Func 또한 내장 델리게이트 중 하나로 입력 인자와 반환 값이 있는 일반적인 델리게이트 유형으로 반환 값을 가지는 메서드를 참조할 때 사용한다.

 

단순히 메서드를 전달 또는 메서드를 전달하고 반환 값을 받는 경우에는 Action이나 Func를 사용하는것이 코드를 간소화할 수 있다.

// Action을 사용하여 메서드를 매개변수로 전달하는 예시
public void PrintMessage(Action<string> messageAction)
{
    messageAction("Hello, world!");
}

// 메서드를 Action으로 전달하고 실행
PrintMessage(message => Console.WriteLine(message)); // "Hello, world!" 출력

// delegate를 쓴경우
public delegate void MessageDelegate(string message);	// 델리게이트 생성부터 필요
public void PrintMessage(MessageDelegate messageDelegate)
{
    messageDelegate("Hello, world!");
}

// 메서드를 delegate로 전달하고 실행
PrintMessage(message => Console.WriteLine(message)); // "Hello, world!" 출력


//==============================================================================



// Func을 사용하여 메서드를 매개변수로 전달하고 반환 값을 받는 예시
public int GetStringLength(Func<string, int> lengthFunc, string str)
{
    return lengthFunc(str);
}

// 메서드를 Func으로 전달하고 반환 값을 받습니다.
int strLength = GetStringLength(str => str.Length, "Hello"); // strLength에 5가 저장됩니다.


// delegate를 쓴경우
public delegate int LengthDelegate(string str);
public int GetStringLength(LengthDelegate lengthDelegate, string str)
{
    return lengthDelegate(str);
}

// 메서드를 delegate으로 전달하고 반환 값을 받습니다.
int strLength = GetStringLength(str => str.Length, "Hello"); // strLength에 5가 저장됩니다.

불필요한 delegate의 선언부를 줄일 수 있게 된다..

 

728x90
반응형

동기와 비동기는 데이터를 주고 받는 방식에 대한 개념이다. 

동기(Synchronous)

동시에 일어나다.

요청과 결과가 동시에 일어난다는 의미를 가진다. 즉 요청을 하게되면 시간이 얼마나 걸리던지 요청한 자리에서 대기한 후 결과가 주어져야 다음으로 넘어가게된다.

 

- 설계가 간단하고 직관적이다.

- 결과가 주어질 때까지 대기해야한다.

 

비동기(Asynchronous)

동시에 일어나지 않는다.

요청과 결과가 동시에 일어나지 않는다는 의미이다. 즉 요청한 자리에서 결과가 주어지지 않으며 작업 처리 단위를 동시에 맞추지 않아도 된다.

 

- 동기보다 복잡하다.

- 결과가 주어지는 시간 동안 다른 작업을 할 수 있다.

 

 

동기는 요청 이후 확실하게 결과 확인이 필요한 상황에서 사용되고 비동기 방식은 요청 이후 결과가 언제 들어오든 상관이 없는 경우에 사용된다.

728x90
반응형

'Program Language' 카테고리의 다른 글

Reserved Word, Keyword  (0) 2023.01.20
Expression, Statement  (0) 2023.01.20

메인 함수

C#의 메인함수는 프로그램의 시작점이다. 

프로그램이 실행될 때 가장 먼저 실행되는 함수로 몇가지 조건을 가진다.

- 클래스 또는 구조체 내부에 선언한다.

- 메인함수는 반드시 static으로 선언되어야하며 클래스 또는 구조체가 static일 필요는 없다.

- 접근제한자는 public일 필요는 없다.

- 반환형은 void, int, Task, Task<int> 형을 가질 수 있다. (Task, Task<int> 의 경우 async 한정자 필요)

- 매개변수는 string[]을 가질 수 있다. 이 매개변수에는 명령어 인자가 포함된다.

 

선언

public static void Main() {}
public static int Main() {}
public static void Main(string[] args) {}
public static int Main(string[] args) {}
public static async Task Main() {}
public static async Task<int> Main() {}
public static async Task Main(string[] args) {}
public static async Task<int> Main(string[] args) {}
//( public 접근 제한자는 일반적으로 사용되는 것이며 필요조건은 아니다.)

반환형

메인 함수는 한정자나 매개변수의 상태에 따라 반환형을 선택할 수 있다.

//매개변수, await을 모두 사용하지 않음
static int Main()
static void Main()

//매개변수, awiat 모두 사용
static async Task<int> Main(string[] args)
static void Main(string[] args)

//매개변수를 사용하고 await만 사용하지 않음
static int Main(string[] args)
static aync Task Main()

//매개변수를 사용하지않고 await만 사용
static async Task<int> Main()
static async Task Main(string[] args)

 

메인 함수의 반환값을 통해 프로그램 실행 결과를 확인할 수 있다.

윈도우에서 프로그램이 실행되면 메인 함수에서 반환되는 모든 값이 환경 변수에 저장된다.

저장된 환경 변수는 배치 파일의 ERRORLEVEL 또는 PowerShell의 $LastExitCode를 사용하여 검색할 수 있다.

 

테스트

int 값 반환 확인

실행하면 0을 반환하도록 메인 함수를 만든다.

class MainReturnValTest
{
    static int Main()
    {
        //...
        return 0;
    }
}

해당 스크립트가 위치한 폴더에서 PowerShell을 열어 스크립트를 실행시킨다.

dotnet run
if ($LastExitCode -eq 0) {
	Write-Host "Execution succeeded"
} else
{
	Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode

비동기 메인 함수

메인 함수를 비동기로 선언하면 컴파일러는 항상 올바른 코드를 실행하는 이점이 있다.

프로그램의 진입점에서 Task 또는 Task<int>를 반환할때 컴파일러는 프로그램 코드에 선언된 진입점 메서드를 호출하는 새로운 진입점을 생성한다.

 

진입점의 이름을 $GeneratedMain이라고 할때 컴파일러는 이 진입점에 대해 다음과 같은 코드를 생성한다.

 

// 결과적으로 동일한 코드를 생성
static Task Main()
static void $GeneratedMain() => Main().GetAwaiter().GetResult();

static Task Main(string[])
private static void $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();

static Task<int> Main()
private static int $GeneratedMain()

static Task<int> Main(string[])
private static int $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();

 

테스트

async 반환값 확인

비동기 함수 호출을 하기 위해서 상용구 코드를 사용하거나 직접 await을 반환하는 메인함수를 만든다.

// boilerplate code
public static void Main()
{
	AsyncConsoleWork().GetAwaiter().GetResult();
}

// or
static async Task<int> Main(string[] args)
{
	return await AsncConsoleWork();
}

// async function return 0
private static async Task<int> AsyncConsoleWork()
{
	return 0;
}

 

코드를 작성한 다음 동일하게 명령어를 입력해보면 결과를 확인할 수 있는데 이번에는 await으로 인한 동작이 추가된걸 확인할 수 있다.

 

 

명령문 인자

다음 방법들로 함수를 정의해서 메인 함수에 인자를 전달할 수 있다.

Main method code Main signature
No return value, no use of await static void Main(string[] args)
Return value, no use of await static int Main(string[] args)
No return value, uses await static async Task Main(string[] args)
Return value, uses await static async Task<int> Main(string[] args)

인자를 사용하지 않는 경우 더 간단하게 함수를 선언할 수 있다.

Main method code Main signature
No return value, no use of await static void Main()
Return value, no use of await static int Main()
No return value, uses await static async Task Main()
Return value, uses await static async TaskMint> Main()
   

또한 Environment.CommandLine 이나 Environment.GetCommandLineArgs를 통해서 콘솔이나 WinForms 응용 프로그램에서 어느 시점에나 인자에 접근할 수 있다. 명령문 인자를 사용할 수 있도록 하기 위해서는 수동으로 메인 함수를 수정해야한다. 

 

💡 메인 함수의 매개변수는 명령문 인자로 문자열 배열이다. 보통은 인자의 존재를 확인할때 문자열 속성인 Lenght 사용한다.

이때 문자열 배열은 null이 올 수 없기 때문에 null 검사하지 않고 Length로만 판단해도 안전하다.

 

문자열 인자는 Convert 클래스나 Parse 함수를 사용해서 숫자 형식으로 변환할 수 있다.

 

long num = Int64.Parse(args[0]);
long num = long.Parse(args[0]);
long num = Convert.ToInt64(s);

 

문자열을 정수로 변환하여 사용하는 예제

인자가 없는 경우 응용 프로그램은 프로그램의 올바른 사용법을 설명하는 메시지를 출력한다.

 

// ---How to using numeric argument---
// Factorial : 정수로 변환된 인자를 팩토리얼 계산후 반환
public class Functions
{
    public static long Factorial(int n)
    {
        if ((n < 0) || (n > 20))
        {
            return -1;
        }

        long tempResult = 1;
        for (int i = 1; i <= n; i++)
        {
            tempResult *= i;
        }
        return tempResult;
    }
}

class Program
{
    static int Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("Please enter a numeric argument.");
            Console.WriteLine("Usage: Factorial <num>");
            return 1;

        }

        int num;
        bool test = int.TryParse(args[0], out num);
        if (!test)
        {
            Console.WriteLine("Please enter a numeric argument.");
            Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }

        long result = Functions.Factorial(num);

        if (result == -1)
        {
            Console.WriteLine("Input must be >= 0 and <= 20.");
        }
        else
        {
            Console.WriteLine($"The Factorial of {num} is {result}.");
        }

        return 0;
    }
}

예제를 테스트 하기위해서 Developer Command Prompt를 사용한다.

시작 메뉴에서 커맨드 창을 실행시킨다.

(프롬프트창은 굳이 위 프로그램을 사용하지 않아도 된다.)

 

💡Command  명령어 : 경로 이동 cd <path>, 드라이브 변경하는방법 C:\>E:

 

스크립트 파일이 위치한 경로로 이동한 후 다음 명령어를 실행한다,

dotnet build

만약 응용 프로그램이 컴파일 오류가 없다면 해당 스크립트파일의 빌드 파일이 실행된다.

팩토리얼 함수가 잘 동작하는지 확인하기 위해서 인자를 전달해본다.

3의 인자를 전달했을때 3!의 계산 결과인 6이 잘 출력되는걸 확인할 수 있다.

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 비동기화 키워드 : async, await  (0) 2023.01.31
C# 클래스 추상화 키워드 : abstract  (0) 2023.01.25
테스트 환경  (0) 2023.01.17
.Net Framework  (0) 2023.01.17
C#  (0) 2023.01.17

+ Recent posts