호출자 정보
class CallerInfo
{
public static void Main()
{
LogMessage("test");
}
//호출자 정보는 '호출하는 측의 정보'를 메서드의 인자로 전달하는 것이다.
//호출자 정보 특성이 명시된 매개변수는 선택적 매개변수 형식이여야 한다.
//컴파일 시점에 값이 치환되어 빌드된다.
static void LogMessage(string text,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine("텍스트 : " + text); //"test" 출력
Console.WriteLine("이 메서드를 호출하는 메서드 이름" + memberName); //Main 출력
Console.WriteLine("호출 파일 경로" + filePath); //파일 경로 출력
Console.WriteLine("메서드가 호출된 라인 번호" + lineNumber); //10 출력
}
}
비동기 호출
async/await 예약어
비동기 호출에 await 예약어가 함께 쓰이면 C# 컴파일러는 이를 인지하고 그 이후의 코드를 묶어서 비동기 호출이 끝난 다음에 실행되도록 코드를 변경해서 컴파일한다. async 예약어가 있으면 컴파일러는 await 예약어를 예약어로 인식한다. async 예약어가 없으면 그냥 식별자로 인식한다.
class AsyncAwaitTest
{
//async 예약어가 없이 await를 사용할 수 없다.
private static async void AwaitRead()
{
using (FileStream fs = new FileStream("test.log", FileMode.Open))
{
byte[] buffer = new byte[fs.Length];
Console.WriteLine("실행 전의 스레드 id : " + Thread.CurrentThread.ManagedThreadId);
await fs.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine("실행 후의 스레드 id : " + Thread.CurrentThread.ManagedThreadId);
//ReadAsync 이하의 처리는 스레드 풀 스레드가 담당하기 때문에 스레드 id가 다르게 나온다.
//이하의 라인은 ReadAsync 비동기 호출이 완료된 후 호출
string txt = Encoding.UTF8.GetString(buffer);
Console.WriteLine(txt);
}
}
public static void Main()
{
AwaitRead();
Console.ReadLine();
}
}
class BCLAsyncTest //WebClient 비동기 호출 async/await 예제
{
public static void Main()
{
AwaitDownloadString();
Console.ReadLine();
}
private static async void AwaitDownloadString()
{
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
//DownloadStringAsync보다 더 간편하게 이용 가능.
string text = await wc.DownloadStringTaskAsync("http://www.naver.com");
Console.WriteLine(text);
}
}
class BCLAsyncTest2 //TCP 서버 비동기 통신 예제
{
public static void Main()
{
TcpListener listener = new TcpListener(IPAddress.Any, 11200);
listener.Start();
while (true)
{
//연결 요청을 받아들인다.
var client = listener.AcceptTcpClient();
ProcessTcpClient(client);
}
}
//NetworkStream 클래스의 ReadAsync와 WriteAsync를 이용하면 간단하게 비동기 통신 구현 가능
private static async void ProcessTcpClient(TcpClient client)
{
NetworkStream ns = client.GetStream();
byte[] buffer = new byte[1024];
int received = await ns.ReadAsync(buffer, 0, buffer.Length);
string txt = Encoding.UTF8.GetString(buffer, 0 ,received);
byte[] sendBuffer = Encoding.UTF8.GetBytes("Hello : " + txt);
await ns.WriteAsync(sendBuffer, 0, sendBuffer.Length);
ns.Close();
}
}
Task, Task<TResult> 타입
Async 메서드의 반환값은 모두 Task 또는 Task<TResult> 유형이다. Task 타입은 반환값이 없는 경우 사용되고, Task<TResult> 타입은 TResult 형식 매개변수로 지정된 반환값이 있는 경우로 구분된다.
class TaskType
{
public static void Main()
{
//스레드 풀에서
ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine("process workitem"); }, null);
Task task1 = new Task(() => { Console.WriteLine("process taskitem"); });
task1.Start();
Task task2 = new Task((obj) => { Console.WriteLine("process taskitem(obj)"); }, null);
task2.Start();
task1.Wait(); //task1의 작업이 끝날 때까지 현재 스레드를 대기한다.
task2.Wait();
//task 객체를 생성할 필요 없이 바로 작업 시작 가능.
Task.Factory.StartNew(() => { Console.WriteLine("process taskitem StartNew"); });
//Task<TResult> 타입은 값을 반환할 수 있다.
Task<int> task3 = new Task<int>(() =>
{
Random rand = new Random((int)DateTime.Now.Ticks);
return rand.Next();
}
);
task3.Start();
task3.Wait();
Console.WriteLine("무작위 숫자 값 : " + task3.Result);
//StartNew<TResult> 도 반환.
Task<int> taskReturn = Task.Factory.StartNew<int>(() => 255);
taskReturn.Wait();
Console.WriteLine(taskReturn.Result);
}
}
Async 메서드가 아닌 경우의 비동기 처리
class TaskAsync
{
//비동기로 처리할 ReadAllTextAsync라는 메서드를 만들어서 파일 경로를 넘겨준다.
private static async void AwaitFileRead(string filePath)
{
string fileText = await ReadAllTextAsync(filePath);
Console.WriteLine(fileText);
}
//Task를 이용하여 넘겨받은 파일 경로로 들어가 텍스트를 읽고 리턴한다.
static Task<string> ReadAllTextAsync(string filePath)
{
return Task.Factory.StartNew(() =>
{
return File.ReadAllText(filePath);
});
}
public static void Main()
{
string filePath = "test.log";
//비동기로 처리되므로 바로 다음 줄 실행
AwaitFileRead(filePath);
Console.ReadLine();
}
}
비동기 호출의 병렬 처리
//비동기 처리 담당 메서드
private static async void DoAsyncTask()
{
var task3 = Method3Async();
var task5 = Method5Async();
//task3, task5가 끝날 때까지 대기한다.
await Task.WhenAll(task3, task5);
//그 이후 실행
Console.WriteLine(task3.Result + task5.Result);
}
//3초 후 3리턴
private static Task<int> Method3Async()
{
return Task.Factory.StartNew( () =>
{
Thread.Sleep(3000);
return 3;
});
}
//5초 후 5리턴
private static Task<int> Method5Async()
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
return 5;
});
}
public static void Main()
{
DoAsyncTask();
Console.WriteLine("먼저 출력되고, 비동기 값 나중에 출력됨.");
Console.ReadLine();
}
}
확장메서드
Static 메서드를 인스턴스 메서드처럼 클래스명 없이 바로 호출할 수 있는 메서드이다. 메서드의 첫 번째 인자에 확장하려는 타입을 넣고 그 앞에 this 키워드를 붙이면 된다.
//현재 날짜로부터 n일을 뺀 날짜를 구해주는 메서드를 가진 헬퍼 클래스.
public static class DateTimeHelper
{
//기존 헬퍼 메서드
public static DateTime MinusDays(DateTime dt, int days)
{
DateTime d = dt.AddDays(-days);
return d;
}
//확장 메서드
//확장하려는 타입이 DateTime이고 그 앞에 this를 붙였다.
public static DateTime MinusDaysEx(this DateTime dt, int days)
{
DateTime d = dt.AddDays(-days);
return d;
}
}
class ExtensionMethod
{
public static void Main()
{
//DateHelper라는 클래스 이름과 함께 MinusDays 메서드를 호출해야한다.
//날짜 연산의 대상이 되는 current 객체를 넘겨줘야 한다.
DateTime current = DateTime.Now;
DateTime dt1 = DateTimeHelper.MinusDays(current, 3);
Console.WriteLine(dt1.ToString());
//확장 메서드를 이용하면 헬퍼 클래스의 이름을 기억해서 호출할 필요가 없다.
//대상이 되는 객체를 인자로 넘겨줄 필요가 없다.
DateTime dt2 = current.MinusDaysEx(3);
Console.WriteLine(dt2.ToString());
Console.ReadKey();
}
}