올바른 아키텍처에 대한 의견을 묻고 싶습니다 Task.Run
. WPF .NET 4.5 응용 프로그램 (Caliburn Micro 프레임 워크 사용)에서 느린 UI가 발생합니다.
기본적으로 나는 (매우 단순화 된 코드 스 니펫) :
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
내가 읽거나 본 기사 / 비디오 await
async
에서 백그라운드 스레드에서 실행되고 있지 않고 백그라운드에서 작업을 시작하기 위해 대기 중으로 래핑해야한다는 것을 알고 있습니다 Task.Run(async () => ... )
. 를 사용 async
await
하면 UI가 차단되지 않지만 여전히 UI 스레드에서 실행 중이므로 지연됩니다.
Task.Run을 놓을 가장 좋은 곳은 어디입니까?
그냥해야 할까
-
.NET의 스레딩 작업이 적기 때문에 외부 호출을 래핑하십시오.
-
또는 내부에서 실행되는 CPU 바인딩 메소드 만 랩핑해야
Task.Run
다른 장소에서 재사용 할 수 있습니까? 코어의 깊은 백그라운드 스레드에서 작업을 시작하는 것이 좋은 아이디어인지 확실하지 않습니다.
광고 (1)에서 첫 번째 해결책은 다음과 같습니다.
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
광고 (2), 두 번째 해결책은 다음과 같습니다.
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
답변
내 블로그에 수집 된 UI 스레드 작업을 수행하기위한 지침을 참고하십시오 .
- 한 번에 50ms 이상 UI 스레드를 차단하지 마십시오.
- UI 스레드에서 초당 최대 100 개의 연속을 예약 할 수 있습니다. 1000이 너무 큽니다.
사용해야하는 두 가지 기술이 있습니다.
1) 가능할 ConfigureAwait(false)
때 사용하십시오 .
예, await MyAsync().ConfigureAwait(false);
대신 await MyAsync();
.
ConfigureAwait(false)
await
현재 컨텍스트에서 다시 시작할 필요가 없음을 알려줍니다 (이 경우 “현재 컨텍스트에서”는 “UI 스레드에서”를 의미 함). 그러나 해당 async
메소드 의 나머지 부분 () 뒤에는 ConfigureAwait
현재 컨텍스트에 있다고 가정하는 작업 (예 : 업데이트 UI 요소)을 수행 할 수 없습니다.
자세한 내용은 MSDN 기사 비동기 프로그래밍의 모범 사례를 참조하십시오 .
2) Task.Run
CPU 바운드 메소드를 호출하는 데 사용 합니다.
Task.Run
재사용 할 수있는 코드 (예 : 라이브러리 코드) 내에 는 사용 하지 않아야합니다 . 따라서 메소드 구현 의 일부가 아닌 메소드 Task.Run
를 호출 하는 데 사용 합니다 .
따라서 순수한 CPU 바운드 작업은 다음과 같습니다.
// Documentation: This method is CPU-bound.
void DoWork();
다음을 사용하여 전화하십시오 Task.Run
.
await Task.Run(() => DoWork());
CPU 바운드와 I / O 바운드 가 혼합 된 메소드 에는 CPU 바운드 Async
특성을 나타내는 문서 가 포함 된 서명 이 있어야합니다 .
// Documentation: This method is CPU-bound.
Task DoWorkAsync();
Task.Run
(부분적으로 CPU 바인딩되어 있기 때문에)를 사용하여 호출 할 수도 있습니다 .
await Task.Run(() => DoWorkAsync());
답변
ContentLoader의 한 가지 문제는 내부적으로 순차적으로 작동한다는 것입니다. 더 나은 패턴은 작업을 병렬화 한 다음 마지막에 동기화하는 것입니다.
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
var tasks = new List<Task>();
tasks.Add(DoCpuBoundWorkAsync());
tasks.Add(DoIoBoundWorkAsync());
tasks.Add(DoCpuBoundWorkAsync());
tasks.Add(DoSomeOtherWorkAsync());
await Task.WhenAll(tasks).ConfigureAwait(false);
}
}
당연히 다른 작업의 데이터가 필요한 작업이있는 경우에는 작동하지 않지만 대부분의 시나리오에서 더 나은 전체 처리량을 제공해야합니다.