|
## **Threading**
|
|
## **Delegate**
|
|
**Thread**는 Process 내부에서 생성되는 명령어를 실행하기 위한 스케줄링의 단위이다. Process를 생성할 때 기본적으로 한 개의 Main Thread를 생성한다. Muti-Core CPU에서는 여러 개의 Thread를 동시에 실행할 수 있어서 이를 활용한 응용 프로그램을 만들 수 있다.
|
|
**Delegate**는 Method를 참조하는 형식이다. **Delegate**에 매개변수와 반환 형식을 정의하고, 이와 같은 구성의 Method를 할당하여 대신 호출해 주는 역할을 한다.
|
|
|
|
|
|
> System.Threading
|
|
|
|
|
|
|
|
### **Thread**
|
|
|
|
**Thread**를 생성하고 실행하는 데 필요한 명령어를 가진 클래스다.
|
|
|
|
|
|
|
|
#### **Foreground Thread**
|
|
|
|
**Foreground Thread**는 Main Thread의 종료와 상관없이 모든 Thread가 종료되어야 Process가 종료된다.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
static void Main()
|
|
delegate int Calculate(int x, int y);
|
|
{
|
|
```
|
|
Console.WriteLine("Main Thread Start");
|
|
```
|
|
|
|
class Mathematics
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.Start();
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc()
|
|
|
|
{
|
|
{
|
|
Console.WriteLine("Foreground Thread Start");
|
|
public int Add(int x, int y)
|
|
|
|
{
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
return x + y;
|
|
|
|
}
|
|
Console.WriteLine("Foreground Thread End");
|
|
|
|
}
|
|
}
|
|
```
|
|
```
|
|
```
|
|
```
|
|
실행 결과 :
|
|
Mathematics math = new Mathematics();
|
|
Main Thread Start
|
|
Calculate calculate = math.Add; // new Calculate(math.Add);
|
|
Foreground Thread Start
|
|
|
|
Main Thread End
|
|
|
|
Foreground Thread End
|
|
|
|
```
|
|
|
|
|
|
|
|
#### **Background Thread**
|
|
int result = calculate(2, 3);
|
|
**Background Thread**는 Thread의 종료 여부와 상관없이 Main Thread가 종료되면 Process도 종료된다. 단, Join()을 사용하면 해당 Thread가 종료할 때까지 현재 Thread는 대기한다.
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(result); // 출력 결과 : 5
|
|
```
|
|
```
|
|
static void Main()
|
|
|
|
{
|
|
|
|
Console.WriteLine("Main Thread Start");
|
|
|
|
|
|
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.IsBackground = true;
|
|
|
|
thread.Start();
|
|
|
|
|
|
|
|
thread.Join(); // thread가 종료할 때까지 대기
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc()
|
|
#### **Delegate Chain**
|
|
{
|
|
`+=` 연산자를 사용해서 같은 형태의 여러 Method를 참조하여 동시에 호출할 수 있다. 반대로 `-=` 연산자를 사용해서 참조를 해제할 수도 있다.
|
|
Console.WriteLine("Background Thread Start");
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine("Background Thread End");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
```
|
|
실행 결과 :
|
|
delegate void MyDelegate();
|
|
Main Thread Start
|
|
|
|
Background Thread Start
|
|
|
|
Background Thread End
|
|
|
|
Main Thread End
|
|
|
|
```
|
|
```
|
|
|
|
|
|
### **Monitor**
|
|
|
|
**Monitor**는 임의의 객체를 잠금으로써 특정 코드의 접근을 막는 기능을 가지고 있다. 여러 Thread가 하나의 공유자원을 동시에 사용하면 문제가 발생하는데 **Monitor**를 통해 하나의 Thread에서만 공유자원을 사용할 수 있도록 하여 Thread를 동기화할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
static void Main()
|
|
class MyClass
|
|
{
|
|
{
|
|
MyData data = new MyData(); // MyData 클래스 생략
|
|
public void funcFirst()
|
|
|
|
|
|
Thread thread1 = new Thread();
|
|
|
|
Thread thread2 = new Thread();
|
|
|
|
|
|
|
|
thread1.Start(data);
|
|
|
|
thread2.Start(data);
|
|
|
|
|
|
|
|
thread1.Join();
|
|
|
|
thread2.Join();
|
|
|
|
|
|
|
|
Console.WriteLine(data.Number);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
|
|
Mydata data = inst as MyData;
|
|
|
|
|
|
|
|
for (int i = 0; i < 100000; i++)
|
|
|
|
{
|
|
{
|
|
Monitor.Enter(data);
|
|
Console.WriteLine("First Function");
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
data.compute(); // MyData의 Method
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
Monitor.Exit(data);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
* `lock` 키워드를 사용하면 try~finally와 Monitor의 역할을 대신 할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
|
|
Mydata data = inst as MyData;
|
|
|
|
|
|
|
|
for (int i = 0; i < 100000; i++)
|
|
public void funcSecond()
|
|
{
|
|
{
|
|
lock (data)
|
|
Console.WriteLine("Second Function");
|
|
{
|
|
|
|
data.compute();
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
```
|
|
|
|
MyClass myClass = new MyClass();
|
|
|
|
|
|
### **Interlocked**
|
|
MyDelegate myDelegate = myClass.funcFirst;
|
|
**Interlocked**는 다중 Thread의 공유자원에 대한 **Atomic Operation**을 제공한다. 특히 64bit 변수의 연산 과정에서 동기화 문제가 생길 수 있는데 **Atomic Operation**을 하면 하나의 Thread에서 한 번에 처리할 수 있다. 이 경우에 **Monitor**를 사용하여 Thread를 동기화할 수 있지만, **Interlocked**에서 제공하는 연산 기능으로 간단하게 구현할 수 있다.
|
|
myDelegate += myClass.funcSecond; // '+=' 연산자 사용
|
|
|
|
|
|
```
|
|
myDelegate();
|
|
static void threadFunc(object inst)
|
|
|
|
{
|
|
// 출력 결과 :
|
|
long number = 0; // 8byte(64bit)
|
|
// First Function
|
|
|
|
// Second Function
|
|
Interlocked.Exchange(ref number, 3); // number = 5;
|
|
|
|
Interlocked.Add(ref number, 5); // number += 5;
|
|
|
|
Interlocked.Increment(ref number); // number++;
|
|
|
|
Interlocked.Decrement(ref number); // number--;
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
|
|
|
|
### **ThreadPool**
|
|
## **Event**
|
|
**ThreadPool**은 필요할 때마다 Thread를 꺼내 쓰고 다시 반환할 수 있는 기능을 제공한다. **Thread**를 따로 생성할 필요 없이 `QueueUserWorkItem()`을 사용하면 Thread가 자동으로 생성되고 지정한 Method가 할당되어 실행한다.
|
|
**Event**는 **Delegate**와 마찬가지로 Method를 참조하는 형식이다. 클래스 내의 특정 Event가 일어났음을 외부에 알려주는 기능을 한다. **Event**에 Method를 추가/제거하여 실행될 명령들을 지정할 수 있다. **Event**는 **Delegate**와 달리 **인터페이스**에서 선언할 수 있는 특징이 있다.
|
|
|
|
|
|
```
|
|
```
|
|
static void Main()
|
|
delegate void EventHandler(int x);
|
|
{
|
|
|
|
Mydata data = new Mydata();
|
|
|
|
|
|
|
|
ThreadPool.QueueUserWorkItem(threadFunc, data);
|
|
|
|
ThreadPool.QueueUserWorkItem(threadFunc, data);
|
|
|
|
|
|
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine(data.Number);
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
|
|
|
|
### **EventWaitHandle**
|
|
|
|
**EventWaitHandle**은 Signal과 Non-Signal의 상태 변화를 통해 Thread를 동기화하는 방법이다. `WaitOn()`에서 Non-Signal이 Signal 상태로 바뀔 때까지 Thread가 더 실행되지 못하게 하고 대기 상태로 만든다. 앞서 나온 **Background Thread**의 `Join()`과 유사한 역할을 한다.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
static void Main()
|
|
class Mathematics
|
|
{
|
|
{
|
|
Console.WriteLine("Main Thread Start");
|
|
public event EventHandler CheckNumber;
|
|
|
|
|
|
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset); // Non-Signal 상태, 수동 Reset
|
|
|
|
|
|
|
|
Thread thread = new Thread(threadFunc);
|
|
|
|
thread.IsBackground = true;
|
|
|
|
thread.Start(ewh);
|
|
|
|
|
|
|
|
ewh.WaitOn(); // Signal 상태로 바뀔 때까지 대기
|
|
public void excute(int x)
|
|
|
|
{
|
|
Console.WriteLine("Main Thread End");
|
|
if (CheckNumber != null)
|
|
|
|
{
|
|
|
|
CheckNumber(x);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
```
|
|
```
|
|
```
|
|
```
|
|
static void threadFunc(object state)
|
|
static void EvenNumber(int x)
|
|
{
|
|
{
|
|
EventWaitHandle ewh = state as EventWaitHandle;
|
|
if (x % 2 == 0)
|
|
|
|
{
|
|
Console.WriteLine("Background Thread Start");
|
|
Console.WriteLine("Even Number");
|
|
|
|
}
|
|
Thread.Sleep(3000); // 3초 동안 실행 중지
|
|
|
|
|
|
|
|
Console.WriteLine("Background Thread End");
|
|
|
|
|
|
|
|
ewh.Set(); // Signal 상태로 전환
|
|
|
|
}
|
|
}
|
|
```
|
|
|
|
```
|
|
|
|
실행 결과 :
|
|
|
|
Main Thread Start
|
|
|
|
Background Thread Start
|
|
|
|
Background Thread End
|
|
|
|
Main Thread End
|
|
|
|
```
|
|
|
|
|
|
|
|
## **Acync**
|
|
|
|
### **Async Method**
|
|
|
|
`acync`와 `await` 키워드를 사용하여 간편하게 **비동기 호출**을 할 수 있다. `async`는 내부에 `await`를 사용했으므로 해당 Method를 **Async Method**로 정의해주는 역할을 하고, `await`는 대기가 완료될 때까지 Method의 실행을 일시 중단하여 비동기를 적용한다.
|
|
|
|
|
|
|
|
```
|
|
static void PositiveNumber(int x)
|
|
public async void AwaitDownloadString()
|
|
|
|
{
|
|
{
|
|
WebClient webClient = new WebClient();
|
|
if (x > 0)
|
|
|
|
{
|
|
string text = await webClient.DownloadStringTaskAsync("http://www.novonetworks.com/");
|
|
Console.WriteLine("Posotive Number");
|
|
|
|
}
|
|
Console.WriteLine(text);
|
|
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
```
|
|
|
|
Mathematics math = new Mathematics();
|
|
|
|
|
|
### **Task**
|
|
math.CheckNumber += EvenNumber;
|
|
**Task**는 Thread Pool을 사용하여 비동기 작업을 구현할 수 있도록 해준다.
|
|
math.CheckNumber += PositiveNumber;
|
|
|
|
|
|
#### **반환 값이 없는 Task**
|
|
math.excute(3);
|
|
```
|
|
// 출력 결과 :
|
|
Task task = new Tack(() => { Thread.Sleep(3000); });
|
|
// Even Number
|
|
task.Start();
|
|
// Posotive Number
|
|
|
|
|
|
|
|
math.excute(2);
|
|
|
|
// 출력 결과 : Posotive Number
|
|
```
|
|
```
|
|
|
|
|
|
* `Task`를 생성할 필요 없이 **Action Delegate**를 전달하고 바로 작업을 시작할 수 있다.
|
|
## **Lambda**
|
|
|
|
**Lambda**는 **Delegate**보다 더 간편하게 **Anonymous Method**를 정의할 수 있는 표현식이다.
|
|
|
|
|
|
```
|
|
```
|
|
Task task = Task.Factory.StartNew(() => { Thread.Sleep(3000); });
|
|
delegate int Calculate(int x, int y);
|
|
```
|
|
```
|
|
|
|
|
|
#### **반환 값이 있는 Task<TResult>**
|
|
|
|
|
|
|
|
```
|
|
```
|
|
Task<int> task = new Task<int>(() =>
|
|
Calculate calculate = (x, y) =>
|
|
{
|
|
{
|
|
Thread.Sleep(3000);
|
|
return x + y;
|
|
return 3;
|
|
};
|
|
});
|
|
|
|
|
|
|
|
task.Start();
|
|
int result = calculate(2, 3);
|
|
|
|
|
|
Console.WriteLine(task.Result); // 출력 결과 : 3
|
|
Console.WriteLine(result); // 출력 결과 : 5
|
|
```
|
|
```
|
|
|
|
|
|
* `Task<TResult>`를 생성할 필요 없이 **Func Delegate**를 전달하고 바로 작업을 시작할 수 있다.
|
|
* 값을 바로 반환할 수 있으면 **return 문**과 중괄호를 생략할 수 있다.
|
|
|
|
|
|
```
|
|
```
|
|
Task<int> task = Task.Factory.StartNew<int>(() =>
|
|
Calculate calculate = (x, y) => x + y;
|
|
{
|
|
|
|
Thread.Sleep(3000);
|
|
|
|
return 3;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
int result = calculate(2, 3);
|
|
|
|
|
|
Console.WriteLine(task.Result); // 출력 결과 : 3
|
|
System.Console.WriteLine(result); // 출력 결과 : 5
|
|
```
|
|
```
|
|
|
|
|
|
### **Async Method가 아닌 경우의 비동기 처리**
|
|
* **Lambda**를 사용해서 **Event**를 구현할 수 있다.
|
|
**Async Method**가 아닌 Method의 반환 형식을 **Task<TResult>**로 바꾸면 `await`를 이용해 **비동기 호출**을 적용할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
public async void AwaitDownloadString()
|
|
delegate void EventHandler(int x);
|
|
{
|
|
```
|
|
string text = await MyDownloadString("http://www.novonetworks.com/");
|
|
```
|
|
|
|
class Mathematics
|
|
Console.WriteLine(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<string> MyDownloadString(string uri)
|
|
|
|
{
|
|
{
|
|
WebClient webClient = new WebClient();
|
|
public event EventHandler CheckNumber;
|
|
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
public void excute(int x)
|
|
{
|
|
{
|
|
return webClient.DownloadString(uri);
|
|
if (CheckNumber != null)
|
|
});
|
|
{
|
|
|
|
CheckNumber(x);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
### **비동기 호출의 병렬 처리**
|
|
|
|
`async`와 `await` 키워드를 사용해 **비동기 호출**을 적용하면 여러 작업을 병렬로 처리할 수 있다.
|
|
|
|
|
|
|
|
```
|
|
```
|
|
static void Main()
|
|
Mathematics math = new Mathematics();
|
|
{
|
|
|
|
DoAsyncTask();
|
|
|
|
|
|
|
|
Console.WriteLine("Main Thread End");
|
|
|
|
}
|
|
|
|
|
|
|
|
static async void DoAsyncTask()
|
|
|
|
{
|
|
|
|
Stopwatch stopWatch = new Stopwatch();
|
|
|
|
|
|
|
|
var task1 = ThreeSeconds();
|
|
|
|
var task2 = FiveSeconds();
|
|
|
|
|
|
|
|
stopWatch.Start();
|
|
|
|
await Task.WhenAll(task1, task2);
|
|
|
|
stopWatch.Stop();
|
|
|
|
|
|
|
|
TimeSpan timeSpan = stopWatch.Elapsed;
|
|
|
|
|
|
|
|
Console.WriteLine("Total : " + (task1.Result + task2.Result));
|
|
|
|
Console.WriteLine("Result : " + timeSpan.Seconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Task<int> ThreeSeconds()
|
|
math.CheckNumber += (x) =>
|
|
{
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
{
|
|
Thread.Sleep(3000);
|
|
if (x % 2 == 0)
|
|
return 3;
|
|
{
|
|
});
|
|
Console.WriteLine("Even Number");
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
static Task<int> FiveSeconds()
|
|
math.CheckNumber += (x) =>
|
|
{
|
|
|
|
return Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
{
|
|
Thread.Sleep(5000);
|
|
if (x > 0)
|
|
return 5;
|
|
{
|
|
});
|
|
Console.WriteLine("Posotive Number");
|
|
}
|
|
}
|
|
```
|
|
};
|
|
```
|
|
|
|
출력 결과 :
|
|
|
|
Main Thread End
|
|
|
|
Total : 8
|
|
|
|
Result : 5
|
|
|
|
```
|
|
|
|
|
|
|
|
## **예제 분석**
|
|
math.excute(3);
|
|
### **SendRecvCommandTimeoutAsync()**
|
|
// 출력 결과 :
|
|
```
|
|
// Even Number
|
|
public async Task<Tuple<bool, int>> SendRecvCommandTimeoutAsync(/* 매개변수 */) { /* 내용 */ }
|
|
// Posotive Number
|
|
```
|
|
|
|
* `acyns`를 사용했으므로 비동기 호출이 가능한 Method이다.
|
|
|
|
* `bool`과 `int` 형식의 값을 저장한 `Tuple` 객체를 가진 Task를 반환한다.
|
|
|
|
|
|
|
|
|
|
math.excute(2);
|
|
|
|
// 출력 결과 : Posotive Number
|
|
```
|
|
```
|
|
Task<int> cmdTask = SendRecvCommandAsync(client, ipAddr, cmd, ctag, sessionID);
|
|
|
|
```
|
|
|
|
* ENC로 부터 PDU를 전송받아 calla_id로 변환된 값을 반환하는 Task인 SendRecvCommandAsync()를 cmdTask에 저장한다.
|
|
|
|
|
|
|
|
---
|
|
### **Action, Func**
|
|
|
|
#### **Action**
|
|
|
|
**Action**은 반환 값이 없는 **Deletegate**이다. `T`에는 매개변수의 형식을 지정한다.
|
|
|
|
|
|
```
|
|
```
|
|
Task timeoutTask = Task.Delay(timeoutMillis);
|
|
Action
|
|
```
|
|
Action <T1>
|
|
* 현재 Method에서 매개변수로 받은 값인 timeoutMillis(기본 값:10000) 만큼 Delay 하는 Task를 timeoutTask에 저장한다.
|
|
Action <T1, T2>
|
|
|
|
…
|
|
---
|
|
Action <T1, T2, …, T16>
|
|
|
|
|
|
```
|
|
```
|
|
result = await Task.Factory.ContinueWhenAny<bool>(new Task[] { cmdTask, timeoutTask }, (completedTask) => { /* 내용 */ });
|
|
|
|
```
|
|
```
|
|
* cmdTask와 timeoutTask를 동시에 실행하여 먼저 끝난 Task를 Func Delegate의 매개변수인 completedTask에 넘긴 후 Task를 실행한다.
|
|
Action<int, int> birthday = (month, day) =>
|
|
* `await`를 사용했으므로 이 모든 Task는 비동기로 처리되고, Task의 반환 값은 result에 저장한다.
|
|
{
|
|
* Task.Factory.ContinueWhenAny()와 관련된 내용과 예제를 설명 하단에 추가하였다.
|
|
DateTime date = new DateTime(DateTime.Now.Year, month, day);
|
|
|
|
Console.WriteLine(date.ToString("yyyy년 MM월 dd일");
|
|
|
|
};
|
|
|
|
|
|
|
|
birthday(3, 20); // 출력 결과 : 2015년 03월 20일
|
|
```
|
|
```
|
|
if (completedTask == timeoutTask)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if
|
|
|
|
{
|
|
|
|
/* 생략 */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
* completedTask와 timeoutTask가 같다는 것은 cmdTask을 작업하는 데 제한시간을 초과했다는 것이므로 `false`를 반환한다. 이외 경우도 시간 초과로 보고 `false`를 반환한다.
|
|
|
|
|
|
|
|
---
|
|
#### **Func**
|
|
|
|
**Func**은 반환 값이 있는 **Deletegate**이다. `T`에는 매개변수의 형식, `TResult`에는 반환 형식을 지정한다.
|
|
|
|
|
|
```
|
|
```
|
|
else if (completedTask == cmdTask)
|
|
Func<TResult>
|
|
{
|
|
Func<T1, TResult>
|
|
calla_id = cmdTask.Result;
|
|
Func<T1, T2, TResult>
|
|
|
|
…
|
|
int isValid = Calla.valid(calla_id);
|
|
Func<T1, T2 …, T16, TResult>
|
|
if (isValid != 1)
|
|
|
|
{
|
|
|
|
Calla.close(calla_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (isValid == 1);
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
* completedTask와 cmdTask가 같다는 것은 제한시간 내에 cmdTask에서 calla_id를 반환했다는 것이다.
|
|
```
|
|
* calla_id가 유효한 ID인지를 확인한다. Invalid(-1)하면 Close하고, Valid(1)하면 `true`를 반환한다.
|
|
Action<int, int, int> add = (x, y) => x + y;
|
|
|
|
|
|
---
|
|
int result = add(2, 3);
|
|
|
|
|
|
|
|
System.Console.WriteLine(result); // 출력 결과 : 5
|
|
```
|
|
```
|
|
return new Tuple<bool, int>(result, calla_id);
|
|
|
|
|
|
## **확장 Method**
|
|
|
|
### **FirstOrDefault()**
|
|
|
|
조건과 일치하는 요소를 검색하여 첫 번째로 나온 값을 반환한다. 단, 값이 없으면 그 형식의 기본값을 반환한다.
|
|
|
|
|
|
```
|
|
```
|
|
* Task.Factory.ContinueWhenAny()에서 반환된 result와 cmdTask에서 반환된 calla_id 변수를 `Tuple`에 저장 후 객체를 생성하여 최종적으로 반환한다.
|
|
int[] array = new int[] { 3, -2, 5, 0, 7 };
|
|
|
|
|
|
#### **Task.Factory.ContinueWhenAny()**
|
|
int result1 = array.FirstOrDefault(item => item < 0);
|
|
예제를 살펴보면 첫 번째 매개변수의 Task 배열 중 먼저 끝난 Task가 두 번째 Func Delegate의 매개변수로 전달되는 것을 알 수 있다.
|
|
int result2 = array.FirstOrDefault(item => item > 10);
|
|
|
|
|
|
|
|
Console.WriteLine(result1); // 출력 결과 : -2
|
|
|
|
Console.WriteLine(result2); // 출력 결과 : 0
|
|
```
|
|
```
|
|
public async void asyncTest()
|
|
|
|
{
|
|
|
|
var task1 = Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(5000);
|
|
|
|
return 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
var task2 = Task.Factory.StartNew(() =>
|
|
### **Where()**
|
|
{
|
|
조건과 일치하는 요소의 값들을 `IEnumerable<T>` 형식으로 반환한다.
|
|
Thread.Sleep(2000);
|
|
|
|
return 2;
|
|
|
|
});
|
|
|
|
|
|
|
|
var task3 = Task.Factory.StartNew(() =>
|
|
|
|
{
|
|
|
|
Thread.Sleep(3000);
|
|
|
|
return 3;
|
|
|
|
});
|
|
|
|
|
|
|
|
int result = await Task.Factory.ContinueWhenAny<int>(new[] { task1, task2, task3 }, (completedTask) =>
|
|
|
|
{
|
|
|
|
int id = -1;
|
|
|
|
|
|
|
|
if (completedTask.Equals(task1))
|
|
```
|
|
{
|
|
int[] array = new int[] { 3, -2, 5, 0, 7 };
|
|
id = task1.Result;
|
|
|
|
}
|
|
|
|
else if (completedTask.Equals(task2))
|
|
|
|
{
|
|
|
|
id = task2.Result;
|
|
|
|
}
|
|
|
|
else if (completedTask.Equals(task3))
|
|
|
|
{
|
|
|
|
id = task3.Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return id;
|
|
var result = array.Where(item => item > 0);
|
|
});
|
|
|
|
|
|
foreach(int item in result)
|
|
Console.WriteLine(result); // 실행 결과 : 2 (task2의 반환 값)
|
|
{
|
|
|
|
Console.Write(item + " ");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 출력 결과 : 3 5 7
|
|
```
|
|
```
|
|
|
|
|
|
### **SendRecvCommandAsync()**
|
|
### **Select()**
|
|
|
|
지정된 식에 해당하는 값을 `IEnumerable<T>` 형식으로 반환한다.
|
|
|
|
|
|
```
|
|
```
|
|
// App.Const.cs
|
|
int[] array = new int[] { 3, -2, 5, 0, 7 };
|
|
|
|
|
|
|
|
var result = array.Select(item => item * 2);
|
|
|
|
|
|
public static class AppConst
|
|
foreach (int item in result)
|
|
{
|
|
{
|
|
// PDU Header
|
|
Console.Write(item + " ");
|
|
public enum PduHeader : int
|
|
|
|
{
|
|
|
|
signature = 0, // [4] SIGNATURE $HI$"
|
|
|
|
version = 4, // [1] VERSION 1
|
|
|
|
type = 5, // [1] VERSION reqTL1(0), rspCalla(1), rspTL1(2)
|
|
|
|
checksun = 6, // [2] CHECKSUM sum(body)
|
|
|
|
session = 8, // [4] SESSION ID
|
|
|
|
ctag = 12, // [4] USER TAG
|
|
|
|
length = 16,// [4] BODY LENGTH
|
|
|
|
Size = 20
|
|
|
|
};
|
|
|
|
|
|
|
|
public const int PduDefaultVersion = 1;
|
|
|
|
public const int PduRequestTL1 = 0;
|
|
|
|
public const int PduResponseCalla = 1;
|
|
|
|
public const int PduSessionFreePass = 999999999;
|
|
|
|
public const int PduSessionInitial = 0;
|
|
|
|
|
|
|
|
// Calla Const
|
|
|
|
public const int CallaHeaderSize = 24;
|
|
|
|
public const int CallaOneRowSize = 1024;
|
|
|
|
|
|
|
|
public const int CallaBufferNumber = 50;
|
|
|
|
public const int CallaBufferSize = (150 * CallaOneRowSize) + CallaHeaderSize;
|
|
|
|
|
|
|
|
// TCP PORT
|
|
|
|
public const int EncTcpPort = 3300;
|
|
|
|
|
|
|
|
/* 생략 */
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 출력 결과 : 6 –4 10 0 14
|
|
```
|
|
```
|
|
```
|
|
|
|
// CRC.CheckSum16() Method 수정
|
|
|
|
|
|
|
|
ushort CheckSum16(byte[] ptr, int len)
|
|
## **LINQ**
|
|
|
|
**LINQ(Language Integrated Query)**는 SQL의 Query를 사용해서 데이터를 조회, 가공할 수 있는 기능이다. **LINQ**는 내부적으로 `IEnumerable<T>`의 **확장 Method**를 표현한 문법이므로, `IEnumerable<T>` 형식이거나 그것을 상속한 객체에서 사용할 수 있다.
|
|
|
|
|
|
|
|
> System.Linq
|
|
|
|
|
|
|
|
```
|
|
|
|
class Student
|
|
{
|
|
{
|
|
uint sum;
|
|
public string Name { get; private set; }
|
|
ushort chsum;
|
|
public int Year { get; private set; }
|
|
byte over;
|
|
public double Grade { get; private set; }
|
|
|
|
|
|
byte index = 0; // 추가
|
|
|
|
|
|
|
|
sum = 0;
|
|
public Student(string name, int year, double grade)
|
|
for (; len > 1; len -= 2)
|
|
|
|
{
|
|
{
|
|
sum += ptr[index];
|
|
Name = name;
|
|
index++;
|
|
Year = year;
|
|
|
|
Grade = grade;
|
|
}
|
|
}
|
|
|
|
|
|
if (len == 1)
|
|
public override string ToString()
|
|
{
|
|
{
|
|
ptr[index] = (byte)(ptr[index] & 0xff00);
|
|
return String.Format("{0} ({1}학년) : {2} 학점", Name, Year, Grade);
|
|
sum += (uint)(ptr[index] >> 8);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Lecture
|
|
|
|
{
|
|
|
|
public string Name { get; private set; }
|
|
|
|
public string Subject { get; private set; }
|
|
|
|
|
|
over = (byte)(sum >> 16);
|
|
public Lecture(string name, string subject)
|
|
chsum = (ushort)(~(over + (ushort)sum));
|
|
{
|
|
|
|
Name = name;
|
|
return chsum;
|
|
Subject = subject;
|
|
|
|
}
|
|
}
|
|
}
|
|
```
|
|
```
|
|
```
|
|
```
|
|
// SendRecvCommandAsync()에 필요한 임의의 변수들 선언
|
|
List<Student> students = new List<Student>
|
|
|
|
{
|
|
TcpClient client = new TcpClient();
|
|
new Student("Kim", 3, 4.3),
|
|
string ipAddr = "192.168.0.100";
|
|
new Student("Lee", 2, 3.5),
|
|
string cmd = "TEST";
|
|
new Student("Park", 4, 3.8),
|
|
int ctag = 1234;
|
|
new Student("Choi", 3, 2.7),
|
|
int sessionID = AppConst.PduSessionFreePass; // 999999999
|
|
new Student("Jeong", 1, 4.1)
|
|
|
|
};
|
|
Task<int> cmdTask = SendRecvCommandAsync(client, ipAddr, cmd, ctag, sessionID);
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
List<Lecture> lectures = new List<Lecture>
|
|
|
|
{
|
|
|
|
new Lecture("Choi", "Java"),
|
|
|
|
new Lecture("Kim", "C#"),
|
|
|
|
new Lecture("Kim", "Network"),
|
|
|
|
new Lecture("Lee", "Database"),
|
|
|
|
new Lecture("Park", "C")
|
|
|
|
};
|
|
```
|
|
```
|
|
public async Task<int> SendRecvCommandAsync(TcpClient client, string ipAddr, string cmd, int ctag, int sessionID) { /* 내용 */ }
|
|
|
|
```
|
|
|
|
* `acyns`를 사용했으므로 비동기 호출이 가능한 Method이다.
|
|
|
|
* `int` 형식의 값을 가진 Task를 반환한다.
|
|
|
|
|
|
|
|
---
|
|
#### **from, select**
|
|
|
|
* **모든 요소를 선택하여 해당 객체로 반환**
|
|
|
|
|
|
```
|
|
```
|
|
string myCmd = "%%" + cmd;
|
|
// SQL Query
|
|
int calla_id = -1;
|
|
// SELECT * FROM students AS student
|
|
```
|
|
|
|
* 전송할 명령어 앞에 기본적으로 "%%"를 추가한다. (ex. %%TEST)
|
|
|
|
* calla_id를 -1로 초기화해준다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
// 확장 Method
|
|
|
|
var result = students.Select((student) => student);
|
|
```
|
|
```
|
|
await client.ConnectAsync(ipAddr, EncClientManager.Port);
|
|
|
|
```
|
|
```
|
|
* IP(ipAddr)와 Port(EncClientManager.Port)에 해당하는 TCP Sever(ENC)에 Client를 연결한다.
|
|
var result = from student in students
|
|
* TCP Server와 연결하는 과정에서 대기시간이 길어지게 되면 병목이 발생하여 프로그램을 실행하는 동안에 문제가 발생할 수 있다. `await`를 사용해 비동기 처리를 함으로써 이러한 문제를 방지할 수 있다.
|
|
select student;
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
foreach (var item in result)
|
|
using (var stream = client.GetStream())
|
|
|
|
{
|
|
{
|
|
/* 내용 */
|
|
Console.WriteLine(item);
|
|
}
|
|
}
|
|
```
|
|
```
|
|
* 연결된 Client에서 데이터를 송수신하는 데 사용되는 Stream을 가져온다.
|
|
|
|
* `using` 키워드를 사용하여 코드가 끝나거나 예외가 발생했을 때 Dispose()를 호출하여 객체의 할당을 자동으로 해제한다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
```
|
|
byte[] cmdBytes = Encoding.UTF8.GetBytes(myCmd);
|
|
출력 결과 :
|
|
int paddedCmdLen = cmdBytes.Length;
|
|
Kim (3학년) : 4.3 학점
|
|
if ((paddedCmdLen % 2) == 1)
|
|
Lee (2학년) : 3.5 학점
|
|
paddedCmdLen++;
|
|
Park (4학년) : 3.8 학점
|
|
|
|
Choi (3학년) : 2.7 학점
|
|
|
|
Jeong (1학년) : 4.1 학점
|
|
```
|
|
```
|
|
* 전송할 명령어를 byte로 Encoding하여 cmdBytes에 저장한다.
|
|
|
|
* cmdBytes의 길이가 홀수이면 Alignment로 인해 명령어의 크기를 1 증가시켜 짝수로 만들어 준다.
|
|
|
|
|
|
|
|
---
|
|
* **일부 요소를 선택하여 해당 형식으로 반환**
|
|
|
|
|
|
```
|
|
```
|
|
byte[] paddedCmdBytes = new byte[paddedCmdLen];
|
|
// SQl Qurey
|
|
Array.Clear(paddedCmdBytes, 0, paddedCmdBytes.Length);
|
|
// SELECT * FROM students AS student
|
|
Array.Copy(cmdBytes, paddedCmdBytes, cmdBytes.Length);
|
|
|
|
```
|
|
|
|
* 명령어 크기만큼의 byte 배열(paddedCmdBytes)을 생성한다.
|
|
|
|
* paddedCmdBytes 배열의 모든 값을 0으로 초기화한다.
|
|
|
|
* cmdBytes 배열 전체를 paddedCmdBytes에 복사한다. paddedCmdBytes에는 Payload를 저장하고 있다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
// 확장 Method
|
|
|
|
var result = students.Select((student) => student.Name);
|
|
```
|
|
```
|
|
byte[] sendBuffer = new byte[PDU_HDR_LEN + paddedCmdBytes.Length];
|
|
|
|
```
|
|
```
|
|
* PDU의 Header와 paddedCmdBytes의 길이를 더한 만큼의 byte 배열(sendBuffer)을 생성한다. sendBuffer는 ENC로 전송될 PDU가 될 것이다.
|
|
var result = from student in students
|
|
|
|
select student.Name;
|
|

|
|
|
|
|
|
|
|
---
|
|
foreach (var item in result)
|
|
|
|
{
|
|
```
|
|
Console.WriteLine(item);
|
|
Array.Copy(PduSignature, sendBuffer, PduSignature.Length);
|
|
}
|
|
```
|
|
```
|
|
* 정의된 PduSignature의 값을 sendBuffer의 첫 번째 요소부터 복사한다.
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
```
|
|
sendBuffer[(int)AppConst.PduHeader.version] = AppConst.PduDefaultVersion;
|
|
출력 결과 :
|
|
|
|
Kim
|
|
|
|
Lee
|
|
|
|
Park
|
|
|
|
Choi
|
|
|
|
Jeong
|
|
```
|
|
```
|
|
* PduDefaultVersion를 sendBuffer의 4번 요소에 저장한다.
|
|
|
|
|
|
|
|

|
|
* **일부 요소를 선택하여 Anonymous Type으로 반환**
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
|
|
sendBuffer[(int)AppConst.PduHeader.type] = AppConst.PduRequestTL1;
|
|
|
|
```
|
|
```
|
|
* PduRequestTL1를 sendBuffer의 5번 요소에 저장한다.
|
|
// SQl Query
|
|
|
|
// SELECT student.Name FROM students AS student
|
|

|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
// 확장 Method
|
|
|
|
var result = students.Select((student) => new { Name = student.Name, Grade = student.Grade });
|
|
```
|
|
```
|
|
ushort checksum = CRC.CheckSum16(paddedCmdBytes, paddedCmdBytes.Length);
|
|
|
|
byte[] checksumBytes = BitConverter.GetBytes(checksum);
|
|
|
|
Array.Reverse(checksumBytes);
|
|
|
|
Array.Copy(checksumBytes, 0, sendBuffer, (int)AppConst.PduHeader.checksun, checksumBytes.Length);
|
|
|
|
```
|
|
```
|
|
* paddedCmdBytes에 대한 CRC 값을 checksum을 저장한다.
|
|
var result = from student in students
|
|
* checksum을 byte 로 Convert 해서 checksumBytes에 저장한다.
|
|
select new { Name = student.Name, Grade = student.Grade };
|
|
* checksumBytesd를 뒤집어서 sendBuffer의 6~7번 요소에 복사한다. (Endian 변환)
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
---
|
|
foreach (var item in result)
|
|
|
|
{
|
|
```
|
|
Console.WriteLine(item);
|
|
byte[] sessBytes = BitConverter.GetBytes(sessionID);
|
|
}
|
|
Array.Reverse(sessBytes);
|
|
|
|
Array.Copy(sessBytes, 0, sendBuffer, (int)AppConst.PduHeader.session, sessBytes.Length);
|
|
|
|
```
|
|
```
|
|
* sessionID를 byte로 Convert 해서 sessBytes에 저장한다.
|
|
|
|
* sessBytes를 뒤집어서 sendBuffer의 8~11번 요소에 복사한다.
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
```
|
|
byte[] ctagBytes = BitConverter.GetBytes(ctag);
|
|
출력 결과 :
|
|
Array.Reverse(ctagBytes);
|
|
{ Name = "Kim", Grade = 4.3 }
|
|
Array.Copy(ctagBytes, 0, sendBuffer, (int)AppConst.PduHeader.ctag, ctagBytes.Length);
|
|
{ Name = "Lee", Grade = 3.5 }
|
|
|
|
{ Name = "Park", Grade = 3.8 }
|
|
|
|
{ Name = "Choi", Grade = 2.7 }
|
|
|
|
{ Name = "Jeong", Grade = 4.1 }
|
|
```
|
|
```
|
|
* ctag를 byte로 Convert 해서 ctagBytes에 저장한다.
|
|
|
|
* ctagBytes를 뒤집어서 sendBuffer의 12~15번 요소에 복사한다.
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
---
|
|
#### **where**
|
|
|
|
```
|
|
|
|
// SQL Query
|
|
|
|
// SELECT * FROM students AS student WHERE student.Year >= 2
|
|
|
|
|
|
|
|
// 확장 Mthod
|
|
|
|
var result = students.Where((student) => student.Year >= 2);
|
|
```
|
|
```
|
|
byte[] lengthBytes = BitConverter.GetBytes(paddedCmdBytes.Length);
|
|
|
|
Array.Reverse(lengthBytes);
|
|
|
|
Array.Copy(lengthBytes, 0, sendBuffer, (int)AppConst.PduHeader.length, lengthBytes.Length);
|
|
|
|
```
|
|
```
|
|
* paddedCmdBytes의 길이를 byte로 Convert 해서 lengthBytes에 저장한다.
|
|
var result = from student in students
|
|
* lengthBytes를 뒤집어서 sendBuffer의 16~19번 요소에 복사한다.
|
|
where student.Year >= 2 // 2학년 이상
|
|
|
|
select student;
|
|

|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
foreach (var item in result)
|
|
|
|
{
|
|
|
|
Console.WriteLine(item);
|
|
|
|
}
|
|
```
|
|
```
|
|
Array.Copy(paddedCmdBytes, 0, sendBuffer, (int)AppConst.PduHeader.Size, paddedCmdBytes.Length);
|
|
|
|
```
|
|
```
|
|
* paddedCmdBytes를 sendBuffer의 20번 요소부터 복사한다.
|
|
출력 결과 :
|
|
* ENC로 전송될 PDU의 정보가 sendBuffer에 모두 저장되었다.
|
|
Kim (3학년) : 4.3 학점
|
|
|
|
Lee (2학년) : 3.5 학점
|
|
|
|
Park (4학년) : 3.8 학점
|
|
|
|
Choi (3학년) : 2.7 학점
|
|
|
|
```
|
|
|
|
|
|

|
|
#### **order by**
|
|
|
|
* **ascending** : 오름차순 (생략 가능), **descending** : 내림차순
|
|
|
|
|
|
---
|
|
```
|
|
|
|
// SQL Query
|
|
|
|
// SELECT * FROM students AS student ORDER BY student.Year DESC
|
|
|
|
|
|
|
|
// 확장 Method
|
|
|
|
var result = students.OrderByDescending((student) => student.Year);
|
|
```
|
|
```
|
|
await stream.WriteAsync(sendBuffer, 0, sendBuffer.Length);
|
|
|
|
```
|
|
```
|
|
* 현재 Stream에 해당하는 ENC로 PDU(sendBuffer)를 비동기 처리로 전송한다.
|
|
var result = from student in students
|
|
|
|
orderby student.Year descending
|
|
|
|
select student;
|
|
---
|
|
|
|
|
|
|
|
|
|
foreach (var item in result)
|
|
|
|
{
|
|
|
|
Console.WriteLine(item);
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
```
|
|
byte[] recvBuffer = new byte[AppConst.CallaBufferSize];
|
|
출력 결과 :
|
|
|
|
Park (4학년) : 3.8 학점
|
|
|
|
Kim (3학년) : 4.3 학점
|
|
|
|
Choi (3학년) : 2.7 학점
|
|
|
|
Lee (2학년) : 3.5 학점
|
|
|
|
Jeong (1학년) : 4.1 학점
|
|
```
|
|
```
|
|
* 전송받을 Calla PDU 크기만큼의 byte 배열(recvBuffer)을 생성한다.
|
|
|
|
|
|
|
|
---
|
|
#### **group by**
|
|
|
|
* **분류 값(Key)과 각 요소들의 객체를 Lookup 형식으로 반환**
|
|
|
|
|
|
```
|
|
```
|
|
int bytes;
|
|
// SQL Query
|
|
|
|
// SELECT * FROM students AS student GROUP BY student.Grade
|
|
|
|
|
|
bytes = await stream.ReadAsync(recvBuffer, 0, PDU_HDR_LEN);
|
|
// 확장 Method
|
|
if (bytes == 0)
|
|
var result = students
|
|
{
|
|
.OrderBy((student) => student.Year)
|
|
Trace.TraceWarning(/* 생략 */);
|
|
.GroupBy((student) => student.Year);
|
|
return -1;
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
* 현재 Stream에 해당하는 ENC로부터 Header 길이만큼의 byte 데이터를 비동기 처리로 전송받는다.
|
|
|
|
* 전송받은 byte 수가 0이면 오류로 판단하여 -1을 반환한다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
```
|
|
bool isValidSignature = true;
|
|
var result = from student in students
|
|
for (int i = 0; i < PduSignature.Length; i++)
|
|
orderby student.Year // ascending
|
|
|
|
group student by student.Year;
|
|
|
|
|
|
|
|
foreach (var group in result)
|
|
{
|
|
{
|
|
if (recvBuffer[i] != PduSignature[i])
|
|
Console.WriteLine(String.Format("[{0}학년]", group.Key));
|
|
|
|
|
|
|
|
foreach (var item in group)
|
|
{
|
|
{
|
|
isValidSignature = false;
|
|
Console.WriteLine(item);
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Console.WriteLine();
|
|
if (!isValidSignature)
|
|
|
|
{
|
|
|
|
Trace.TraceWarning(/* 생략 */);
|
|
|
|
return -1;
|
|
|
|
}
|
|
}
|
|
```
|
|
```
|
|
* 정의된 Signature와 recvBuffer의 Signature가 같은지 확인하고, 다르면 오류로 판단하여 -1을 반환한다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
|
|
byte version;
|
|
|
|
byte type;
|
|
|
|
ushort recvChecksum;
|
|
|
|
int sessID;
|
|
|
|
int userTag;
|
|
|
|
int bodyLength;
|
|
|
|
```
|
|
```
|
|
* PDU의 Header 정보를 저장할 각각의 변수를 선언한다.
|
|
출력 결과 :
|
|
|
|
[1학년]
|
|
|
|
Jeong (1학년) : 4.1 학점
|
|
|
|
|
|
---
|
|
[2학년]
|
|
|
|
Lee (2학년) : 3.5 학점
|
|
|
|
|
|
```
|
|
[3학년]
|
|
version = recvBuffer[(int)AppConst.PduHeader.version];
|
|
Kim (3학년) : 4.3 학점
|
|
type = recvBuffer[(int)AppConst.PduHeader.type];
|
|
Choi (3학년) : 2.7 학점
|
|
|
|
|
|
if (type != AppConst.PduResponseCalla)
|
|
[4학년]
|
|
{
|
|
Park (4학년) : 3.8 학점
|
|
Trace.TraceWarning(/* 생략 */);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
```
|
|
```
|
|
* recvBuffer의 4번 요소의 값을 version에 저장한다.
|
|
|
|
* recvBuffer의 5번 요소의 값을 type에 저장한다.
|
|
|
|
* type이 1(Calla Response)이 아니면 오류로 판단하여 -1을 반환한다.
|
|
|
|
|
|
|
|
---
|
|
* **분류된 요소들을 Anonymous Type으로 반환**
|
|
|
|
|
|
```
|
|
```
|
|
Array.Reverse(recvBuffer, (int)AppConst.PduHeader.checksun, 2);
|
|
// SQL Query
|
|
recvChecksum = BitConverter.ToUInt16(recvBuffer, (int)AppConst.PduHeader.checksun);
|
|
// SELECT student.Name, student.Grade FROM students AS student GROUP BY student.Grade ORDER BY student.Year
|
|
```
|
|
|
|
* recvBuffer의 6~7번 요소의 값을 뒤집은 후 UInt16으로 Convert 해서 recvChecksum에 저장한다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
// 확장 Method
|
|
|
|
var result = students
|
|
|
|
.OrderBy((student) => student.Year)
|
|
|
|
.GroupBy((student) => student.Year, (student) => new { Name = student.Name, Grade = student.Grade });
|
|
```
|
|
```
|
|
Array.Reverse(recvBuffer, (int)AppConst.PduHeader.session, 4);
|
|
|
|
sessID = BitConverter.ToInt32(recvBuffer, (int)AppConst.PduHeader.session);
|
|
|
|
```
|
|
```
|
|
* recvBuffer의 8~11번 요소의 값을 뒤집은 후 Int32로 Convert 해서 sessID에 저장한다.
|
|
var result = from student in students
|
|
|
|
orderby student.Year // ascending
|
|
|
|
group new { Name = student.Name, Grade = student.Grade } by student.Year;
|
|
|
|
|
|
---
|
|
foreach (var group in result)
|
|
|
|
|
|
```
|
|
|
|
Array.Reverse(recvBuffer, (int)AppConst.PduHeader.ctag, 4);
|
|
|
|
userTag = BitConverter.ToInt32(recvBuffer, (int)AppConst.PduHeader.ctag);
|
|
|
|
|
|
|
|
if (userTag != ctag)
|
|
|
|
{
|
|
{
|
|
Trace.TraceWarning(/* 생략 */);
|
|
Console.WriteLine(String.Format("[{0}학년]", group.Key));
|
|
return -1;
|
|
|
|
|
|
foreach (var item in group)
|
|
|
|
{
|
|
|
|
Console.WriteLine(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
Console.WriteLine();
|
|
}
|
|
}
|
|
```
|
|
```
|
|
* recvBuffer의 12~15번 요소의 값을 뒤집은 후 Int32로 Convert 해서 userTag에 저장한다.
|
|
```
|
|
* ENC로 전송했던 User Tag(ctag)와 userTag가 다르면 오류로 판단하여 -1을 반환한다.
|
|
출력 결과 :
|
|
|
|
[1학년]
|
|
|
|
{ Name = "Jeong", Grade = 4.1 }
|
|
|
|
|
|
---
|
|
[2학년]
|
|
|
|
{ Name = "Lee", Grade = 3.5 }
|
|
|
|
|
|
|
|
[3학년]
|
|
|
|
{ Name = "Kim", Grade = 4.3 }
|
|
|
|
{ Name = "Choi", Grade = 2.7 }
|
|
|
|
|
|
|
|
[4학년]
|
|
|
|
{ Name = "Park", Grade = 3.8 }
|
|
```
|
|
```
|
|
Array.Reverse(recvBuffer, (int)AppConst.PduHeader.length, 4);
|
|
|
|
bodyLength = BitConverter.ToInt32(recvBuffer, (int)AppConst.PduHeader.length);
|
|
|
|
```
|
|
|
|
* recvBuffer의 16~19번 요소의 값을 뒤집은 후 Int32로 Convert 해서 bodyLength에 저장한다.
|
|
|
|
|
|
|
|
|
|
#### **join**
|
|
|
|
```
|
|
|
|
// SQL Query
|
|
|
|
// SELECT student.Name, student.Year, lecture.Subject FROM students AS student JOIN lectures AS lecture ON student.Name = lecture.Name
|
|
|
|
|
|
---
|
|
// 확장 Method
|
|
|
|
var result = students
|
|
|
|
.Join(lectures, (student) => student.Name, (lecture) => lecture.Name, (student, lecture) => new { student, lecture })
|
|
|
|
.Select((join) => new { Name = join.student.Name, Year = join.student.Year, Subject = join.lecture.Subject });
|
|
```
|
|
```
|
|
int remainLength = bodyLength;
|
|
```
|
|
int destIndex = 0;
|
|
var result = from student in students
|
|
|
|
join lecture in lectures on student.Name equals lecture.Name
|
|
|
|
select new { Name = student.Name, Year = student.Year, Subject = lecture.Subject };
|
|
|
|
|
|
while (remainLength > 0)
|
|
foreach (var item in result)
|
|
{
|
|
{
|
|
int readAmount = await stream.ReadAsync(recvBuffer, destIndex, remainLength);
|
|
Console.WriteLine(item);
|
|
if (readAmount == 0)
|
|
|
|
{
|
|
|
|
Trace.TraceWarning(/* 생략 */);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
remainLength -= readAmount;
|
|
|
|
destIndex += readAmount;
|
|
|
|
}
|
|
}
|
|
```
|
|
```
|
|
* 전송받은 후 남은 데이터의 길이를 확인할 변수인 remainLength에 Payload의 길이를 저장하고, 현재까지 전송받은 데이터의 길이를 확인할 변수인 destIndex에 0을 초기화 해준다.
|
|
|
|
* 이 전에 ReadAsync()를 통해 Header 부분은 전송을 받았으므로, Body 부분부터 전송을 받을 것이다. 전송받은 index(destIndex)부터 남은 데이터의 길이(remainLength)만큼의 byte 데이터를 비동기 처리로 전송받는다.
|
|
|
|
* 한 번에 많은 데이터를 전송받지 못 하므로 readAmount에 전송받은 데이터의 길이를 저장하여 남은 길이를 확인할 것이다.
|
|
|
|
* 남은 데이터의 길이(remainLength)가 있음에도 전송받은 데이터(readAmount)가 없으면 오류로 판단하여 -1을 반환한다.
|
|
|
|
* 남은 데이터의 길이(remainLength)에서 전송받은 데이터의 길이(readAmount)를 제외한다. 또한, 다음에 전송받은 데이터를 이어서 저장하기 위해 recvBuffer의 index를 확인하는 변수인 destIndex에 전송받은 데이터의 길이(readAmount)를 더해준다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
```
|
|
```
|
|
if (!Calla.SafeExtract(recvBuffer, bodyLength, out calla_id))
|
|
출력 결과 :
|
|
{
|
|
{ Name = "Kim", Year = 3, Subject = "C#" }
|
|
; // TODO.... PDU overflow or PDU crashed...
|
|
{ Name = "Kim", Year = 3, Subject = "Network" }
|
|
}
|
|
{ Name = "Lee", Year = 2, Subject = "Database" }
|
|
|
|
{ Name = "Park", Year = 4, Subject = "C" }
|
|
|
|
{ Name = "Choi", Year = 3, Subject = "Java" }
|
|
```
|
|
```
|
|
* 전송받은 Payload를 Parsing해서 calla_id에 저장한다.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### **LINQ 예시 분석**
|
|
```
|
|
```
|
|
bool isValid = (Calla.valid(calla_id) == 1);
|
|
var groupTemp = from data in TxPowerItems
|
|
|
|
group data by data.Path into newData
|
|
|
|
orderby newData.Key
|
|
|
|
select new
|
|
|
|
{
|
|
|
|
Key = newData.Key,
|
|
|
|
Temps = newData.ToList()
|
|
|
|
};
|
|
```
|
|
```
|
|
* calla_id가 유효한 ID인지를 확인한다. (Valid = 1, Invalid = -1)
|
|
|
|
|
|
|
|
---
|
|
* 조회할 'TxPowerItems' 객체를 'data'로 사용한다. 'data'의 'Path'를 기준으로 분류하여 'newData'를 생성한다.
|
|
|
|
|
|
```
|
|

|
|
return calla_id;
|
|
|
|
```
|
|
* **group by**로 분류된 'newData'에는 'Key'와 각 요소를 저장한 'Temps'가 저장된다. 'newData'의 'Key'는 오름차순으로 정렬되어 있다.
|
|
* SendRecvCommandAsync()는 최종적으로 calla_id를 반환한다. |
|
|
|
\ No newline at end of file |
|
 |
|
|
|
\ No newline at end of file |