비동기 호출
-
asynchronous call & synchronous call
multi process(thread)환경에서 각 process(thread)는 독립적인 실행 흐름을 가지고 동작하는데, 한 process(thread)가 다른 process(thread)에게 하부작업(function, method)를 호출할 경우 호출한 process(thread)의 실행 흐름 중지 여부에 따라 동기, 비동기를 구분 짓게 된다. synchronous call이란 호출 process(thread)의 실행 흐름이 멈추게 되는 호출을 말하고, asynchronous call이란 하부작업의 실행 또는 종료와 관계없이 호출 process(thread)의 실행 흐름은 계속되는 호출을 말한다. -
async/ await keyword를 사용한 asynchronous call
C#의 await keyword가 Task, Task<TResult>
타입을 반환하는 method를 대상으로 비동기 처리를 자동화 하였다.
class FileState
{
public byte[] Buffer;
public FileStream File;
}
class Program
{
static void Main(string[] args)
{
using (FileStream fs = new FileStream(@"C:\Users\Jaehyun\Desktop\user.txt", FileMode.Open))
{
//synchronous call
byte[] buf = new byte[fs.Length];
fs.Read(buf, 0, buf.Length);
string txt = Encoding.UTF8.GetString(buf);
Console.WriteLine(txt);
//asynchronous call
FileState state = new FileState();
state.Buffer = new byte[fs.Length];
state.File = fs;
byte[] asybuf = new byte[fs.Length];
fs.Position = 0;
fs.BeginRead(state.Buffer, 0, state.Buffer.Length, readCompleted, state);
//await async call!
Console.ReadLine();
}
Console.WriteLine("Enter AwaitRead()");
AwaitRead();
Console.WriteLine("Exit AwaitRead()");
}
private static async void AwaitRead()
{
using (FileStream fs = new FileStream(@"C:\Users\Jaehyun\Desktop\user2.txt", FileMode.Open))
{
byte[] buf = new byte[fs.Length];
//닷넷 4.5 BCL에 추가된 Async method
await fs.ReadAsync(buf, 0, buf.Length);
string txt = Encoding.UTF8.GetString(buf);
Console.WriteLine(txt);
}
}
private static void readCompleted(IAsyncResult ar)
{
FileState state = ar.AsyncState as FileState;
state.File.EndRead(ar);
string txt = Encoding.UTF8.GetString(state.Buffer);
Console.WriteLine(txt);
}
}
-
Task, Task
<TResult>
타입class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { Task.Factory.StartNew(() => { Console.WriteLine("Process taskitem"); }); Person person = new Person(); person.Name = "Sujin"; person.Age = 44; Task.Factory.StartNew((obj) => { Console.WriteLine("Process taskitem({0} : {1})", ((Person)obj).Name, ((Person)obj).Age); }, person); //Task type은 비동기 처리를 위한 내부적인 Type. ThreadPool의 QueueUserWorkItem과 차별화된 점이라면 좀 더 세밀하게 제어할 수 있다. Task<int> task = new Task<int> ( () => { Random rand = new Random((int)DateTime.Now.Ticks); return rand.Next(); } ); task.Start(); task.Wait(); Console.WriteLine(task.Result); } }
-
Async method가 아닌 경우의 비동기 처리
public delegate string ReadAllTextDelegate(string path); static void Main(string[] args) { string filePath = @"C:\Users\Jaehyun\Desktop\user.txt"; ReadAllTextDelegate func = File.ReadAllText; func.BeginInvoke(filePath, actionCompleted, func); Console.ReadLine(); //await 비동기처리 AwaitFileRead(filePath); } private static async void AwaitFileRead(string filePath) { string fileText = await ReadALLTextAsync(filePath); Console.WriteLine(fileText); } //await 비동기처리 static Task<string> ReadALLTextAsync(string filePath) { return Task.Factory.StartNew( () => { return File.ReadAllText(filePath); }); } private static void actionCompleted(IAsyncResult ar) { ReadAllTextDelegate func = ar.AsyncState as ReadAllTextDelegate; string fileText = func.EndInvoke(ar); Console.WriteLine(fileText); }
-
비동기 호출의 병렬 처리
static void Main(string[] args) { //await 비동기 처리 DoAsyncTask(); // Task 처리 Task<int> task5 = Task.Factory.StartNew<int>( () => { Thread.Sleep(5000); return 5; }); Task<int> task3 = Task.Factory.StartNew<int>( () => { Thread.Sleep(3000); return 3; }); Task.WaitAll(task3, task5); Console.WriteLine("{0}, {1}", task3.Result, task5.Result); Console.ReadLine(); } private static async void DoAsyncTask() { Task<int> task6 = Task.Factory.StartNew<int>( () => { Thread.Sleep(6000); return 6; }); Task<int> task2 = Task.Factory.StartNew<int>( () => { Thread.Sleep(2000); return 2; }); await Task.WhenAll(task2, task6); Console.WriteLine("async {0}, {1}", task2.Result, task6.Result); }
C# 6.0
C# 6.0 구문을 사용한 C# 소스코드는 반드시 C# 6.0에서 compile해야 하지만 생성된 결과물은 C# 2.0의 것과 다르지 않기 때문에 실행은 설치된 닷넷 프레임워크의 버전 제약을 받지 않는다.
public class Person
{
public string Name { get; } = "Jane";
public Person()
{
Name = "Nulee";
}
}
위의 코드를 빌드하면 C#6.0 컴파일러는 내부적으로 읽기 전용 속성의 필드로 변경해 컴파일한다.
public class Person
{
private readonly string _name = "Jane";
public string Name
{
get { return _name; }
}
public Person()
{
_name = "Nulee";
}
}
-
람다 식을 이용한 method, property 및 index 정의
C# 언어에는 클래스가 직관적으로 배열처럼 다뤄질 수 있을 때 사용하기 쉽도록 this keyword를 이용한 indexer라고 하는 특별한 구문을 제공한다. 생성자의 경우 method긴 하지만 예외적으로 람다 식을 이용해 구현할 수 없다.public class Vector { double x, y; public Vector(double x, double y) { this.x = x; this.y = y; } public double Angle => Math.Atan2(y, x); public double this[string angleType] => angleType == "radian" ? this.Angle : angleType == "degree" ? RadinToDegree(this.Angle) : double.NaN; public double RadinToDegree(double angle) => angle * (180.0 / Math.PI); public override string ToString() { return string.Format("x = {0}, y = {1}", x, y); } public Vector Move(double dx, double dy) => new Vector(x + dx, y + dy); public void PrintIt() => Console.WriteLine(this); } class Program { static void Main(string[] args) { Vector vec = new Vector(90, 180); vec.Move(1, 1); vec.PrintIt(); Console.WriteLine(vec["radian"]); Console.WriteLine(vec["degree"]); Console.WriteLine(vec["error"]); } }
-
using static 구문을 이용한 타입명 생략
기존의 static member를 사용하는 경우 반드시 타입명과 함께 써야만 했다. 소스코드 파일 범위 내에서는 static type의 정적 member를 타입명없이 바로 호출할 수 있다.using System; using static System.Console; using static ConsoleApplication1.BitMode; using static ConsoleApplication1.MyDay; namespace ConsoleApplication1 { public enum MyDay { Saturday, Sunday, // enum field 내부 구현은 static 속성을 갖는다. } public class BitMode { public const int ON = 1; public const int OFF = 0; public int Fail; } class Program { static void Main(string[] args) { Console.WriteLine("Test 1"); WriteLine("Test 2"); int bit = ON; MyDay day = Sunday; } } }
-
null 조건 연산자
null 값 확인 용도의 if문을 대폭 줄일 수 있다.public class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { PrintObject(null); } private static void PrintObject(Object obj) { Person person = obj as Person; Console.WriteLine(person?.Name); Console.WriteLine(person?.Age); //Console.WriteLine(person != null ? new int?(person.Age) : null); } }
-
문자열 내에 식을 포함
class Person { public string Name { get; set; } public int Age { get; set; } public override string ToString() { //return string.Format("이름 : {0} 나이 : {1}", Name, Age); //return $"{{이름 : {Name}, 나이 : {Age}}}"; //return $"{{이름 : {Name.ToUpper()}, 나이 : {(Age > 19 ? "성년" : "미성년")}}}"; //return $"{{이름 : {Name.ToUpper(), -10}, 나이 : {(Age > 19 ? "성년" : "미성년"), 5}}}"; return $"이름 : {Name.ToUpper(),-10}, 나이 : {Age,5:X}"; } } class Program { static void Main(string[] args) { Person person = new Person { Name = "Nulee", Age = 49 }; Console.WriteLine(person); } }
-
nameof 연산자
public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } public override string ToString() { return $"이름 : {Name}, 나이 : {Age}"; } } class Program { static void OutputPerson(string Name, int age) { Console.WriteLine($"{nameof(OutputPerson)} {nameof(Name)} == {Name}, {nameof(age)} == {age}"); } static void Main(string[] args) { OutputPerson("Cat", 67); Person person = new Person("Dog", 1); Console.WriteLine(person); Console.WriteLine($"{nameof(Person)} 속성 : {nameof(Person.Name)}, {nameof(Person.Age)}"); } }
실행 결과
OutputPersonName == Cat, age == 67
이름 : Dog, 나이 : 1
Person 속성 : Name, Age
Press any key to continue . . . -
Dictionary 타입의 인덱스 초기화
var weekends1 = new Dictionary<int, string> { { 0, "Sunday"}, { 1, "Monday"}, { 1, "Monday"}, // 불가능 weekends1.Add(1, "Monday")를 호출하기 때문에 예외발생 }; var weekends2 = new Dictionary<int, string> { [0] = "Sunday", [6] = "Saturday", [6] = "Monday", // 인텍서 방식의 코드로 변경되기 때문에 가능 };
-
예외 필터
static void Main(string[] args) { string filePath = @"C:\Users\Jaehyun\Desktop\null.txt"; try { string txt = File.ReadAllText(filePath); } catch (FieldAccessException e) when (Log(e)) { Console.WriteLine("실행됨"); } catch (FileNotFoundException e) when (filePath.IndexOf("Users") != -1) { Console.WriteLine("실행됨"); } } private static bool Log(FieldAccessException e) { Console.WriteLine(e.ToString()); return false; }
-
collection 초기화 구문에 extend method로 정의한 Add 지원
public class NaturalNumber : IEnumerable { List<int> numbers = new List<int>(); public List<int> Numbers { get { return numbers; } } public IEnumerator GetEnumerator() { return ((IEnumerable)numbers).GetEnumerator(); } } public static class NaturalNumberExtension { public static void Add(this NaturalNumber instance, int number) { instance.Numbers.Add(number); } } class Program { static void Main(string[] args) { NaturalNumber numbers = new NaturalNumber() { 0, 1, 2, 3, 4 }; } }
NaturalNumber 타입의 개발자가 아닐경우 상속 등의 우회적인 방법을 써야 한다. C# 6.0에는 Add method를
ICollection<T>
interface가 없다면 확장 method로 구현돼 있는지 한 번 더 찾는 기능을 추가했다. -
#pragma의 "CS" 접두사 지원
//기존에는 경고를 끄려면 0168 숫자만 넣어야 했다. #pragma warning disable 0168 #pragma warning disable CS0168 #pragma warning disable CS1998
-
재정의된 method의 선택 정확도를 향상
class Program
{
static void WriteLine(uint? arg)
{
Console.WriteLine("uint? == " + arg);
}
static void WriteLine(int? arg)
{
//C# 5.0에서도 이 method가 선택되기 때문에
Console.WriteLine("int? == " + arg);
}
static void Main(string[] args)
{
WriteLine(null);
}
}
class Program
{
static Task NullTask() => null;
static void Main(string[] args)
{
Task.Run(NullTask);
}
}
Task.Run method는 인자가 다른 8개의 중복 정의 method가 제공되는데, 그중에서 다음의 두 가지 메서드 가운데
public static Task Run(Func<Task> function);
public static Task Run(Action action);
NullTask 인자에 대해 어떤 것을 선택해야 하는지 판단할 수 없어 아래와 같은 컴파일 오류가 발생한다. 하지만 C# 6.0부터는 첫 번째 유형인 Run(Func) method(반환값이 있는 delegate)를 선택해 컴파일을 성공시킨다.