[C#] Task.Run을 올바르게 사용할 때와 async-await 만있는 경우

올바른 아키텍처에 대한 의견을 묻고 싶습니다 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을 놓을 가장 좋은 곳은 어디입니까?

그냥해야 할까

  1. .NET의 스레딩 작업이 적기 때문에 외부 호출을 래핑하십시오.

  2. 또는 내부에서 실행되는 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.RunCPU 바운드 메소드를 호출하는 데 사용 합니다.

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);
    }
}

당연히 다른 작업의 데이터가 필요한 작업이있는 경우에는 작동하지 않지만 대부분의 시나리오에서 더 나은 전체 처리량을 제공해야합니다.


답변