AsyncPackage를 사용하여 백그라운드에서 VSPackage 로드

VS 패키지를 로드하고 초기화하면 디스크 I/O가 발생할 수 있습니다. 이러한 I/O가 UI 스레드에서 발생하는 경우 응답성 문제가 발생할 수 있습니다. 이 문제를 해결하기 위해 Visual Studio 2015는 백그라운드 스레드에서 패키지를 로드하는 AsyncPackage 클래스를 도입했습니다.

AsyncPackage 만들기

시작하려면 먼저 VSIX 프로젝트를 만들고(파일>새로 만들기>프로젝트>Visual C#>확장성>VSIX 프로젝트) 프로젝트에 VSPackage를 추가합니다(프로젝트를 마우스 오른쪽 단추로 클릭하고 추가>새 항목>C# 항목>확장성>Visual Studio 패키지). 그런 다음, 서비스를 만들고 해당 서비스를 패키지에 추가할 수 있습니다.

  1. AsyncPackage에서 패키지를 파생합니다.

  2. 쿼리로 인해 패키지가 로드될 수 있는 서비스를 제공하는 경우:

    Visual Studio에게 패키지가 백그라운드 로드에 안전함을 나타내고 이 동작을 옵트인하려면 특성 생성자에서 PackageRegistrationAttributeAllowsBackgroundLoading 속성을 true로 설정해야 합니다.

    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    
    

    Visual Studio에게 백그라운드 스레드에서 서비스를 인스턴스화하는 것이 안전함을 나타내려면 ProvideServiceAttribute 생성자에서 IsAsyncQueryable 속성을 true로 설정해야 합니다.

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. UI 컨텍스트를 통해 로드하는 경우 ProvideAutoLoadAttributePackageAutoLoadFlags.BackgroundLoad를 지정하거나 패키지의 자동 로드 항목 값으로 기록된 플래그에 값(0x2)을 지정해야 합니다.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. 수행할 비동기 초기화 작업이 있는 경우 InitializeAsync를 재정의해야 합니다. VSIX 템플릿에서 제공하는 Initialize() 메서드를 제거합니다. (AsyncPackageInitialize() 메서드가 봉인됩니다.) AddService 메서드 중 하나를 사용하여 패키지에 비동기 서비스를 추가할 수 있습니다.

    참고: base.InitializeAsync()를 호출하기 위해 소스 코드를 다음으로 변경할 수 있습니다.

    await base.InitializeAsync(cancellationToken, progress);
    
  5. 비동기 초기화 코드(InitializeAsync 내)에서 RPC(원격 프로시저 호출)를 수행하지 않도록 주의해야 합니다. 이러한 문제는 GetService을 직접 또는 간접적으로 호출할 때 발생할 수 있습니다. 동기화 로드가 필요한 경우 UI 스레드는 JoinableTaskFactory 사용을 차단합니다. 기본 차단 모델은 RPC를 사용하지 않도록 설정합니다. 즉, 비동기 작업에서 RPC를 사용하려고 하면 UI 스레드 자체가 패키지 로드를 기다리는 경우 교착 상태가 발생합니다. 일반적인 대안은 필요한 경우 조인 가능한 작업 팩터리SwitchToMainThreadAsync 또는 RPC를 사용하지 않는 다른 메커니즘을 사용하여 코드를 UI 스레드로 마샬링하는 것입니다. ThreadHelper.Generic.Invoke를 사용하지 않거나 일반적으로 UI 스레드에 도착하기를 기다리는 호출 스레드를 차단합니다.

    참고: InitializeAsync 메서드에서 GetService 또는 QueryService를 사용하지 않아야 합니다. 이들을 사용해야 하는 경우 먼저 UI 스레드로 전환해야 합니다. 대안은 AsyncPackage에서 GetServiceAsync를 사용하는 것입니다(IAsyncServiceProvider로 캐스팅함).

    C#: AsyncPackage 만들기:

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
public sealed class TestPackage : AsyncPackage
{
    protected override Task InitializeAsync(System.Threading.CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        this.AddService(typeof(SMyTestService), CreateService, true);
        return Task.FromResult<object>(null);
    }
}

기존 VSPackage를 AsyncPackage로 변환

대부분의 작업은 새 AsyncPackage를 만드는 것과 동일합니다. 위의 1~5단계를 따릅니다. 또한 다음 권장 사항에 각별히 주의해야 합니다.

  1. 패키지에 있는 Initialize 재정의를 제거해야 합니다.

  2. 교착 상태를 방지합니다. 코드에는 이제 백그라운드 스레드에서 실행되는 숨겨진 RPC가 있을 수 있습니다. RPC(예: GetService)를 만드는 경우 (1) 주 스레드로 전환하거나 (2) 이미 있는 경우 비동기 버전의 API(예: GetServiceAsync)을 사용해야 합니다.

  3. 스레드 사이를 너무 자주 전환하지 마세요. 백그라운드 스레드에서 발생할 수 있는 작업을 지역화하여 로드 시간을 줄입니다.

AsyncPackage에서 서비스 쿼리

AsyncPackage는 호출자에 따라 비동기적으로 로드되거나 그렇지 않을 수 있습니다. 예를 들면 다음과 같습니다.

  • 호출자가 GetService 또는 QueryService(둘 다 동기 API)를 호출한 경우 또는

  • 호출자가 IVsShell::LoadPackage(또는 IVsShell5::LoadPackageWithContext)를 호출한 경우 또는

  • 로드가 UI 컨텍스트에 의해 트리거되지만 비동기적으로 로드할 수 있는 UI 컨텍스트 메커니즘을 지정하지 않은 경우

    그러면 패키지가 동기적으로 로드됩니다.

    패키지는 여전히 (비동기 초기화 단계에서) UI 스레드에서 작업을 수행할 수 있는 기회가 있지만 해당 작업의 완료를 위해 UI 스레드가 차단됩니다. 호출자가 IAsyncServiceProvider를 사용하여 서비스를 비동기적으로 쿼리하는 경우 결과 작업 개체에서 즉시 차단되지 않는다고 가정하여 로드 및 초기화가 비동기적으로 수행됩니다.

    C#: 서비스를 비동기적으로 쿼리하는 방법:

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

IAsyncServiceProvider asyncServiceProvider = Package.GetService(typeof(SAsyncServiceProvider)) as IAsyncServiceProvider;
IMyTestService testService = await asyncServiceProvider.GetServiceAsync(typeof(SMyTestService)) as IMyTestService;