네! await를 사용함으로써 비동기 메소드 안에서 또 따른 비동기 함수가 동기적으로 작동하기 때문이에요. 하지만 최상단 메소드 역시 비동기 메소드이기 때문에 메인스레드를 블라킹하지 않고 데이터가 UI에 뿌려지는게 보이는 거랍니다. 비동기 메서드 내의 비동기 함수에 await 키워드를 사용하지 않으면 첫번째 함수의 내부 로직이 모두 실행되기 전에 두번째 함수가 호출되고 마찬가지로 세번째 함수가 호출되므로 서로 교차하여 출력되게 되는 것입니다.
C# 관련 영상을 올려주셔서 정말 감사합니다. 비동기 관련해서 질문이 있는데 답변 해주시면 감사드리겠습니다 [1] async 비동기 메서드 public async static void RunWithawait(Label lbl) { for (int i = 0; i< 30; i++) { await Task.Delay(100); lbl.Text = i.ToString(); } } async와 await를 사용하여 비동기 메서드를 생성할 수 있다고 하셨는데, await 키워드를 통해서 0.1초 딜레이를 하고 있지만, 실제 메인 스레드를 사용하고 있는 것이 아닌지 궁금합니다. 실제로 currentThreadId를 확인해보면 메인 스레드와 동일한 스레드를 사용하는데, async 키워드가 붙은 메서드는 무조건 새로운 스레드를 만드는 것과는 다르게 봐야하는지와 만약 메인 스레드를 사용한다면 왜 UI Freezing 현상이 나타나지 않는지 알고 싶습니다. [2] Task.Run() 비동기 메서드 public async static void RunAsync(Label lbl) { await Task.Run(() => { for(int i = 0; i< 30; i++) { Thread.Sleep(100); if (lbl.InvokeRequired) lbl.Invoke(new Action(delegate { lbl.Text = i.ToString(); })); else lbl.Text = i.ToString(); } }); } [2] 메서드처럼 Task.Run() 으로 처리했을 때, 백그라운드 스레드가 새로 생성되어 메서드가 동작하는 것인지 궁금합니다. (currentThreadID는 다른 것을 확인하였습니다.) 실제 비동기 메서드를 구현한다고 하면 [1] 메서드가 아닌 [2] 메서드 처럼 Task.Run()을 사용해서 동작하도록 해야하는 것이 아닌지와 [1]과 [2] 메서드의 정확한 차이점을 알고 싶습니다. 질문이 난해할 수 있는데 비동기 메서드를 만드는 시점과 차이점이 궁금해서 문의드립니다. 감사합니다.
안녕하세요~! 질문 내용이 좋네요! 간단하게 설명해드리겠습니다. Task.Delay() 와 같은 메소드는 비동기적으로 동작을 하는데요. 스래드를 블로킹하지 않습니다. 그 말은 즉 await Task.Delay(10000) 이라고 하면 `나는 잠시 멈춰있을게 같은 쓰레드에 있는 다른 코드 먼저 일하고 있어! 10초가 지나면 내가 다시 동작할게!` 라는 말이 됩니다. 10초가 지난 후 만약 UI를 수정하는 작업이 일어나게 된다고 가정 하겠습니다. --- --- --- --- --- --- --- --- --- --- lbl.Text = i.ToString(); // 실행하는데 약 0.001 초 걸렸다고 가정 --- --- --- --- --- --- --- --- --- --- 아주 빠른 속도로 UI가 변경되고 다시 Task.Delay(10000)을 만나게 됩니다. `화면은 바꿔놨어! 다른 작업하고 있어도 돼 10초후에 나는 움직일게!` 상태가 됩니다. 그래서 화면상에서 UI가 블로킹되지 않는 것 처럼 느끼게 되는것이지 실제로는 lbl.Text = i.ToString(); 이라는 코드의 작업이 3초 이상 걸리는 작업이라면 3초동안 블로킹이 되게 됩니다. Task.Run() 은 위의 예시같이 3초 이상 걸릴만한 아주 무거운 작업을 할 때 백그라운드 쓰래드를 사용하기 위한 용도로 사용됩니다. 예시로 다른 비동기 코드를 알아보도록 할게요. public async Task DownloadFileAsync(string fileUrl, string filePath) { using (var client = new HttpClient()) { using (var response = await client.GetAsync(fileUrl)) { using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { await response.Content.CopyToAsync(fileStream); } } } } 위와 같은 코드가 있다고 가정하겠습니다. URL에서 파일을 다운 받는 코드인데요. CopyToAsync 는 같은 일반적으로 같은 쓰래드에서 파일을 다운로드합니다. 하지만 저 메소드를 만든 개발자는 `파일 크기가 큰` 경우 `I/O 쓰래드 풀`(다른 쓰래드)을 사용하게 만들어 놨습니다. 한마디로 비동기 자체는 메인 쓰래드를 블로킹하지 않지만 너무 무거운 동작이 있으면 그만큼 대기해야하니 그 땐 다른 쓰래드를 사용하는 것이지요. 반면 같은 쓰래드를 사용하더래두 육안으로 표시가 안된다면 그것을 위해 새로운 쓰래드를 할당하는건 리소스 낭비로 볼 수 있겠죠 ~! 일반적으로 이미 구현되어있는 Async 메소드 (PostAsync, GetAsync 등)은 내부적으로 새로운 쓰래드를 할당하게끔 구현되어 있기 때문에 Task.Run과 같은 작업을 할 필요는 없습니다. 하지만 사용자가 직접 만든 코드의 동작이 너무 커서 직접 구현을 하신다면 선택적으로 사용할 수 있겠습니다. 길게 적었는데 이해가 잘 되셨을까요? 혹시라도 궁금한게 더 있으시다면 답글 달아주세요 ^^!
@@kaburi-coder 정말 정말 명쾌한 답변 감사합니다ㅠ 비동기 관련해서 매번 코드는 작성할 수 있어도 정확한 이해를 하지 못하는 것 같았는데, 코더님 덕분에 정리가 되는 것 같아요!! 많은 책이나 예제들이 작업을 하고 있다는 것을 표현하기 위해 Thread.Sleep 메서드나 await Task.Delay() 메서드를 사용하는데, 시간이 아주 적게 걸리는 작업들이 뒤에 따라오는 경우가 많아 조금 헷갈렸던 부분이 있었습니다. 답변 주신 내용으로 정리하자면 1. Task.Delay() 메서드는 스레드를 블로킹 하지 않고 사용한다. 2. 뒤에 따라오는 동작 (UI 수정 또는 단순한 )이 빠른 시간 내에 동작할 수 있다면, 실제로 블로킹이 되지만 사용자에게는 느껴지지 않을 만큼 처리 된다. 3. 대부분의 MS나 다른 라이브러리에서 제공하는 OOOAsync 메서드는 자체적으로 무거운 작업이나 오래 걸리는 작업을 처리할 때 새로운 스레드를 할당해서 사용한다. 4. 개발자 스스로가 비동기 함수를 만들기 위해 await Task.Run(()=>{}); 구문을 사용하는 경우는 3번과 마찬가지로 오래 걸리거나 리소스가 추가로 필요한 경우 사용한다. 인 것 같습니다. 추가적으로 Task.Run() 메서드를 어디에 작성하는 것이 좋은 코드 작성 법인지 궁금합니다. 상황에 따라서 그리고 메서드의 목적과 추가적인 기능 구현에 따라다 다르겠지만, 따로 비동기 메서드를 구현할 때 설계 방법이 있는지 궁금합니다. [ 코드 1 ] 1) 비동기 메서드 구현 public async static void RunAsync() { Task.Run(()=>{ //기능 구현 ... }) } 2) 사용 이후 RunAsync() 호출하여 함수 바로 사용 [ 코드 2 ] 1) 동기 메서드 구현 public static void Run() { //기능 구현 ... } 2) 동기 메서드를 구현하고 비동기 메서드를 만들어서 Task.Run(()=>{}) 메서드 사용 public async static void RunAsyc() { Task.Run(()=>{ Run(); }) } 정말 감사합니다!
@@ianhan7689 네 제가 전달하고자 하는 의미를 모두 잘 이해하신 것 같네요. 런타임 환경마다 조금씩 다를 수 있지만 전체적인 흐름 정도로 파악하시면 될 것 같습니다. 코드 1, 코드 2의 예제로 질문을 하셨는데요! 기능 구현 코드가 복잡한 로직이라면 당연히 코드 2로 작성을 하셔야 하고 비교적 짧고 간단하다면 코드 1로 작성하셔도 무방하실 듯 싶습니다. 코드 1과 코드 2는 가독성면에서 차이를 보인다고 보이네요. ^^ 하지만 짚고 넘어가야 할 점이 있는데요. 현재 작성해주신 코드를 살펴보면 모든 메서드가 `async void` 로 작성이 된 걸 볼 수 있습니다. 해당 부분은 예외 처리가 어렵고, 호출자가 작업이 완료될 때까지 대기할 수 없으므로 일반적으론 권장되지 않는 형태입니다. `async Task`를 사용하여 코드를 변경해주시는게 좋을 듯 싶습니다. public static async Task RunAsyc() // void가 Task로 변경 됨. { // Task.Run()에 await가 추가 await Task.Run(()=>{ Run(); }) } 이렇게 코드가 작성되어 있어야 호출자에서 비동기 코드를 제어할 수 있습니다. 예) static async Task Main(){ Task task1 = RunAsync(); Task task2 = RunAsync2(); Task task3 = RunAsync3(); ..... await Task.WhenAll(task1, task2, task3...); // Task가 모두 완료가 되었는지 확인이 가능 } 이상입니다 ^^ 혹시라도 궁금한 게 더 있으시다면 댓글 달아주세요.
잘 보고갑니다.
와 비동기를 이렇게 쉽게 설명해주시다니, 감사합니다.
설명을 정말 잘 해 주셔서 이해가 한번에 되네요 정말 감사합니다.
늦은 시간까지 열심히 공부하시네요 ~! 영상을 시청해주셔서 감사하고 좋은 말씀 또한 감사드립니다 ^^
callback 함수도 설명해주시면 좋을거같습니다!!
주말에 봐야겠습니다. 출근 준비 중...
감사합니다 도움이 되셨으면 좋겠습니다~!
9분30초에서 await으로 실행시 하나씩 출력되는 이유가 해당 메소드가 다끝날때까지 기다리는거죠? 그리고 await키워드 없이 실행했을때 3개가 동시에 나오는건 함수3개를 모두 호출해서 하나씩 출력되는건가요?
네! await를 사용함으로써 비동기 메소드 안에서 또 따른 비동기 함수가 동기적으로 작동하기 때문이에요.
하지만 최상단 메소드 역시 비동기 메소드이기 때문에 메인스레드를 블라킹하지 않고 데이터가 UI에 뿌려지는게 보이는 거랍니다.
비동기 메서드 내의 비동기 함수에 await 키워드를 사용하지 않으면 첫번째 함수의 내부 로직이 모두 실행되기 전에 두번째 함수가 호출되고 마찬가지로 세번째 함수가 호출되므로 서로 교차하여 출력되게 되는 것입니다.
C# 관련 영상을 올려주셔서 정말 감사합니다.
비동기 관련해서 질문이 있는데 답변 해주시면 감사드리겠습니다
[1] async 비동기 메서드
public async static void RunWithawait(Label lbl)
{
for (int i = 0; i< 30; i++)
{
await Task.Delay(100);
lbl.Text = i.ToString();
}
}
async와 await를 사용하여 비동기 메서드를 생성할 수 있다고 하셨는데, await 키워드를 통해서 0.1초 딜레이를 하고 있지만, 실제 메인 스레드를 사용하고 있는 것이 아닌지 궁금합니다.
실제로 currentThreadId를 확인해보면 메인 스레드와 동일한 스레드를 사용하는데, async 키워드가 붙은 메서드는 무조건 새로운 스레드를 만드는 것과는 다르게 봐야하는지와 만약 메인 스레드를 사용한다면 왜 UI Freezing 현상이 나타나지 않는지 알고 싶습니다.
[2] Task.Run() 비동기 메서드
public async static void RunAsync(Label lbl)
{
await Task.Run(() =>
{
for(int i = 0; i< 30; i++)
{
Thread.Sleep(100);
if (lbl.InvokeRequired) lbl.Invoke(new Action(delegate
{
lbl.Text = i.ToString();
}));
else lbl.Text = i.ToString();
}
});
}
[2] 메서드처럼 Task.Run() 으로 처리했을 때, 백그라운드 스레드가 새로 생성되어 메서드가 동작하는 것인지 궁금합니다. (currentThreadID는 다른 것을 확인하였습니다.) 실제 비동기 메서드를 구현한다고 하면 [1] 메서드가 아닌 [2] 메서드 처럼 Task.Run()을 사용해서 동작하도록 해야하는 것이 아닌지와 [1]과 [2] 메서드의 정확한 차이점을 알고 싶습니다.
질문이 난해할 수 있는데 비동기 메서드를 만드는 시점과 차이점이 궁금해서 문의드립니다.
감사합니다.
안녕하세요~! 질문 내용이 좋네요!
간단하게 설명해드리겠습니다.
Task.Delay() 와 같은 메소드는 비동기적으로 동작을 하는데요. 스래드를 블로킹하지 않습니다.
그 말은 즉 await Task.Delay(10000) 이라고 하면
`나는 잠시 멈춰있을게 같은 쓰레드에 있는 다른 코드 먼저 일하고 있어! 10초가 지나면 내가 다시 동작할게!` 라는 말이 됩니다.
10초가 지난 후 만약 UI를 수정하는 작업이 일어나게 된다고 가정 하겠습니다.
--- --- --- --- --- --- --- --- --- ---
lbl.Text = i.ToString(); // 실행하는데 약 0.001 초 걸렸다고 가정
--- --- --- --- --- --- --- --- --- ---
아주 빠른 속도로 UI가 변경되고 다시
Task.Delay(10000)을 만나게 됩니다.
`화면은 바꿔놨어! 다른 작업하고 있어도 돼 10초후에 나는 움직일게!` 상태가 됩니다.
그래서 화면상에서 UI가 블로킹되지 않는 것 처럼 느끼게 되는것이지 실제로는 lbl.Text = i.ToString();
이라는 코드의 작업이 3초 이상 걸리는 작업이라면 3초동안 블로킹이 되게 됩니다.
Task.Run() 은 위의 예시같이 3초 이상 걸릴만한 아주 무거운 작업을 할 때 백그라운드 쓰래드를 사용하기 위한 용도로 사용됩니다.
예시로 다른 비동기 코드를 알아보도록 할게요.
public async Task DownloadFileAsync(string fileUrl, string filePath)
{
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(fileUrl))
{
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await response.Content.CopyToAsync(fileStream);
}
}
}
}
위와 같은 코드가 있다고 가정하겠습니다.
URL에서 파일을 다운 받는 코드인데요.
CopyToAsync 는 같은 일반적으로 같은 쓰래드에서 파일을 다운로드합니다.
하지만 저 메소드를 만든 개발자는
`파일 크기가 큰` 경우 `I/O 쓰래드 풀`(다른 쓰래드)을 사용하게 만들어 놨습니다.
한마디로 비동기 자체는 메인 쓰래드를 블로킹하지 않지만 너무 무거운 동작이 있으면 그만큼 대기해야하니 그 땐 다른 쓰래드를 사용하는 것이지요.
반면 같은 쓰래드를 사용하더래두 육안으로 표시가 안된다면 그것을 위해 새로운 쓰래드를 할당하는건 리소스 낭비로 볼 수 있겠죠 ~!
일반적으로 이미 구현되어있는 Async 메소드 (PostAsync, GetAsync 등)은 내부적으로 새로운 쓰래드를 할당하게끔 구현되어 있기 때문에 Task.Run과 같은 작업을 할 필요는 없습니다.
하지만 사용자가 직접 만든 코드의 동작이 너무 커서 직접 구현을 하신다면 선택적으로 사용할 수 있겠습니다.
길게 적었는데 이해가 잘 되셨을까요? 혹시라도 궁금한게 더 있으시다면 답글 달아주세요 ^^!
@@kaburi-coder 정말 정말 명쾌한 답변 감사합니다ㅠ
비동기 관련해서 매번 코드는 작성할 수 있어도 정확한 이해를 하지 못하는 것 같았는데, 코더님 덕분에 정리가 되는 것 같아요!!
많은 책이나 예제들이 작업을 하고 있다는 것을 표현하기 위해 Thread.Sleep 메서드나 await Task.Delay() 메서드를 사용하는데, 시간이 아주 적게 걸리는 작업들이 뒤에 따라오는 경우가 많아 조금 헷갈렸던 부분이 있었습니다.
답변 주신 내용으로 정리하자면
1. Task.Delay() 메서드는 스레드를 블로킹 하지 않고 사용한다.
2. 뒤에 따라오는 동작 (UI 수정 또는 단순한 )이 빠른 시간 내에 동작할 수 있다면, 실제로 블로킹이 되지만 사용자에게는 느껴지지 않을 만큼 처리 된다.
3. 대부분의 MS나 다른 라이브러리에서 제공하는 OOOAsync 메서드는 자체적으로 무거운 작업이나 오래 걸리는 작업을 처리할 때 새로운 스레드를 할당해서 사용한다.
4. 개발자 스스로가 비동기 함수를 만들기 위해 await Task.Run(()=>{}); 구문을 사용하는 경우는 3번과 마찬가지로 오래 걸리거나 리소스가 추가로 필요한 경우 사용한다.
인 것 같습니다.
추가적으로 Task.Run() 메서드를 어디에 작성하는 것이 좋은 코드 작성 법인지 궁금합니다. 상황에 따라서 그리고 메서드의 목적과 추가적인 기능 구현에 따라다 다르겠지만, 따로 비동기 메서드를 구현할 때 설계 방법이 있는지 궁금합니다.
[ 코드 1 ]
1) 비동기 메서드 구현
public async static void RunAsync()
{
Task.Run(()=>{ //기능 구현 ... })
}
2) 사용
이후 RunAsync() 호출하여 함수 바로 사용
[ 코드 2 ]
1) 동기 메서드 구현
public static void Run()
{
//기능 구현 ...
}
2) 동기 메서드를 구현하고 비동기 메서드를 만들어서 Task.Run(()=>{}) 메서드 사용
public async static void RunAsyc()
{
Task.Run(()=>{
Run();
})
}
정말 감사합니다!
@@ianhan7689 네 제가 전달하고자 하는 의미를 모두 잘 이해하신 것 같네요.
런타임 환경마다 조금씩 다를 수 있지만 전체적인 흐름 정도로 파악하시면 될 것 같습니다.
코드 1, 코드 2의 예제로 질문을 하셨는데요!
기능 구현 코드가 복잡한 로직이라면 당연히 코드 2로 작성을 하셔야 하고
비교적 짧고 간단하다면 코드 1로 작성하셔도 무방하실 듯 싶습니다.
코드 1과 코드 2는 가독성면에서 차이를 보인다고 보이네요. ^^
하지만 짚고 넘어가야 할 점이 있는데요.
현재 작성해주신 코드를 살펴보면
모든 메서드가 `async void` 로 작성이 된 걸 볼 수 있습니다.
해당 부분은 예외 처리가 어렵고, 호출자가 작업이 완료될 때까지 대기할 수 없으므로 일반적으론 권장되지 않는 형태입니다.
`async Task`를 사용하여 코드를 변경해주시는게 좋을 듯 싶습니다.
public static async Task RunAsyc() // void가 Task로 변경 됨.
{
// Task.Run()에 await가 추가
await Task.Run(()=>{
Run();
})
}
이렇게 코드가 작성되어 있어야 호출자에서 비동기 코드를 제어할 수 있습니다.
예)
static async Task Main(){
Task task1 = RunAsync();
Task task2 = RunAsync2();
Task task3 = RunAsync3();
.....
await Task.WhenAll(task1, task2, task3...);
// Task가 모두 완료가 되었는지 확인이 가능
}
이상입니다 ^^ 혹시라도 궁금한 게 더 있으시다면 댓글 달아주세요.
@@kaburi-coder 와! 바쁘실텐데 하나 하나 정확히 알려주셔서 진심으로 감사의 말씀드립니다.
앞으로도 채널 영상 보면서 많이 배우겠습니다! 즐거운 주말 보내세요~