다음을 통해 공유


비동기 Visual Studio 서비스 제공

UI 스레드를 차단하지 않고 서비스를 가져오려면 비동기 서비스를 만들고 백그라운드 스레드에 패키지를 로드해야 합니다. 이를 위해 Package가 아닌 AsyncPackage를 사용하고 비동기 패키지의 특수 비동기 메서드를 사용하여 서비스를 추가할 수 있습니다.

동기 Visual Studio 서비스 제공에 대한 자세한 내용은 방법: 서비스 제공을 참조하세요.

비동기 서비스 구현

  1. VSIX 프로젝트를 만듭니다(파일>신규>프로젝트>Visual C#>확장성 >VSIX 프로젝트). 프로젝트 이름을 TestAsync로 지정합니다.

  2. 프로젝트에 VSPackage를 추가합니다. 솔루션 탐색기에서 프로젝트 노드를 선택하고 추가>새 항목>Visual C# 항목>확장성>Visual Studio 패키지를 클릭합니다. 이 파일의 이름을 TestAsyncPackage.cs로 지정합니다.

  3. TestAsyncPackage.cs에서 Package가 아닌 AsyncPackage에서 상속하도록 패키지를 변경합니다.

    public sealed class TestAsyncPackage : AsyncPackage
    
  4. 서비스를 구현하려면 다음 세 가지 형식을 만들어야 합니다.

    • 서비스를 식별하는 인터페이스. 이러한 인터페이스의 대부분은 비어 있습니다. 즉, 서비스 쿼리에만 사용되므로 메서드가 없습니다.

    • 서비스 인터페이스를 설명하는 인터페이스 이 인터페이스에는 구현할 메서드가 포함됩니다.

    • 서비스와 서비스 인터페이스를 모두 구현하는 클래스

  5. 다음 예제에서는 세 가지 형식의 매우 기본적인 구현을 보여줍니다. 서비스 클래스의 생성자는 서비스 공급자를 설정해야 합니다. 이 예제에서는 패키지 코드 파일에 서비스를 추가하기만 하면 됩니다.

  6. 패키지 파일에 다음 using 지시문을 추가합니다.

    using System.Threading;
    using System.Threading.Tasks;
    using System.Runtime.CompilerServices;
    using System.IO;
    using Microsoft.VisualStudio.Threading;
    using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider;
    using Task = System.Threading.Tasks.Task;
    
  7. 다음은 비동기 서비스 구현입니다. 생성자의 동기 서비스 공급자 대신 비동기 서비스 공급자를 설정해야 합니다.

    public class TextWriterService : STextWriterService, ITextWriterService
    {
        private IAsyncServiceProvider asyncServiceProvider;
    
        public TextWriterService(IAsyncServiceProvider provider)
        {
            // constructor should only be used for simple initialization
            // any usage of Visual Studio service, expensive background operations should happen in the
            // asynchronous InitializeAsync method for best performance
            asyncServiceProvider = provider;
        }
    
        public async Task InitializeAsync(CancellationToken cancellationToken)
        {
            await TaskScheduler.Default;
            // do background operations that involve IO or other async methods
    
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            // query Visual Studio services on main thread unless they are documented as free threaded explicitly.
            // The reason for this is the final cast to service interface (such as IVsShell) may involve COM operations to add/release references.
    
            IVsShell vsShell = this.asyncServiceProvider.GetServiceAsync(typeof(SVsShell)) as IVsShell;
            // use Visual Studio services to continue initialization
        }
    
        public async Task WriteLineAsync(string path, string line)
        {
            StreamWriter writer = new StreamWriter(path);
            await writer.WriteLineAsync(line);
            writer.Close();
        }
    }
    
    public interface STextWriterService
    {
    }
    
    public interface ITextWriterService
    {
        System.Threading.Tasks.Task WriteLineAsync(string path, string line);
    }
    

서비스 등록

서비스를 등록하려면 서비스를 제공하는 패키지에 ProvideServiceAttribute를 추가합니다. 동기 서비스를 등록하는 것과 달리 패키지와 서비스가 모두 비동기 로드를 지원하는지 확인해야 합니다.

  • 패키지를 비동기식으로 초기화할 수 있도록 AllowsBackgroundLoading = true 필드를 PackageRegistrationAttribute에 추가해야 합니다. PackageRegistrationAttribute에 대한 자세한 내용은 VSPackages 등록 및 등록 취소를 참조하세요.

  • 서비스 인스턴스를 비동기식으로 초기화할 수 있도록 IsAsyncQueryable = true 필드를 ProvideServiceAttribute에 추가해야 합니다.

    다음은 비동기 서비스 등록이 있는 AsyncPackage의 예입니다.

[ProvideService((typeof(STextWriterService)), IsAsyncQueryable = true)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(TestAsyncPackage.PackageGuidString)]
public sealed class TestAsyncPackage : AsyncPackage
{. . . }

서비스 추가

  1. TestAsyncPackage.cs에서 Initialize() 메서드를 제거하고 InitializeAsync() 메서드를 재정의합니다. 서비스를 추가하고 콜백 메서드를 추가하여 서비스를 만듭니다. 다음은 서비스를 추가하는 비동기 이니셜라이저의 예입니다.

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    }
    
    

    이 서비스를 이 패키지 외부에서 표시하려면 승격 플래그 값을 마지막 매개 변수로 true 설정합니다. this.AddService(typeof(STextWriterService), CreateTextWriterService, true);

  2. Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll에 대한 참조를 추가합니다.

  3. 서비스를 만들고 반환하는 비동기 메서드로 콜백 메서드를 구현합니다.

    public async Task<object> CreateTextWriterService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType)
    {
        TextWriterService service = new TextWriterService(this);
        await service.InitializeAsync(cancellationToken);
        return service;
    }
    
    

서비스 사용

이제 서비스를 가져와서 해당 메서드를 사용할 수 있습니다.

  1. 이것은 이니셜라이저에 표시되지만 서비스를 사용하려는 곳 어디에서든 서비스를 가져올 수 있습니다.

    protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    
        ITextWriterService textService = await this.GetServiceAsync(typeof(STextWriterService)) as ITextWriterService;
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
    }
    
    

    잊지 말고 userpath를 컴퓨터에 적합한 파일 이름과 경로로 변경하세요.

  2. 코드를 빌드하고 실행합니다. Visual Studio의 실험적 인스턴스가 나타나면 솔루션을 엽니다. 이로 인해 AsyncPackage가 자동으로 로드됩니다. 이니셜라이저가 실행되면 지정한 위치에서 파일을 찾을 수 있습니다.

명령 처리기에서 비동기 서비스 사용

메뉴 명령에서 비동기 서비스를 사용하는 방법의 예는 다음과 같습니다. 여기에 표시된 절차를 사용하여 다른 비동기 메서드에서 서비스를 사용할 수 있습니다.

  1. 프로젝트에 메뉴 명령을 추가합니다. (솔루션 탐색기에서 프로젝트 노드를 선택하고 마우스 오른쪽 버튼을 클릭한 다음, 추가>새 항목>확장성>사용자 지정 명령을 선택합니다.) 명령 파일의 이름을 TestAsyncCommand.cs로 지정합니다.

  2. 사용자 지정 명령 템플릿은 명령을 초기화하기 위해 Initialize() 메서드를 TestAsyncPackage.cs 파일에 다시 추가합니다. Initialize() 메서드에서 명령을 초기화하는 줄을 복사합니다. 다음과 같이 표시됩니다.

    TestAsyncCommand.Initialize(this);
    

    이 줄을 AsyncPackageForService.cs 파일의 InitializeAsync() 메서드로 이동합니다. 이것은 비동기식 초기화 중이므로 명령을 초기화하기 전에 SwitchToMainThreadAsync를 사용하여 주 스레드로 전환해야 합니다. 이제 다음과 같이 표시됩니다.

    
    protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    
        ITextWriterService textService =
           await this.GetServiceAsync(typeof(STextWriterService)) as ITextWriterService;
    
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
    
        await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        TestAsyncCommand.Initialize(this);
    }
    
    
  3. Initialize() 메서드를 삭제합니다.

  4. TestAsyncCommand.cs 파일에서 MenuItemCallback() 메서드를 찾습니다. 메서드의 본문을 삭제합니다.

  5. using 지시문을 추가합니다.

    using System.IO;
    
  6. 서비스를 가져오고 해당 메서드를 사용하는 UseTextWriterAsync()라는 비동기 메서드를 추가합니다.

    private async System.Threading.Tasks.Task UseTextWriterAsync()
    {
        // Query text writer service asynchronously to avoid a blocking call.
        ITextWriterService textService =
           await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(STextWriterService))
              as ITextWriterService;
    
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
       }
    
    
  7. MenuItemCallback() 메서드에서 이 메서드를 호출합니다.

    private void MenuItemCallback(object sender, EventArgs e)
    {
        UseTextWriterAsync();
    }
    
    
  8. 솔루션을 빌드하고 디버깅을 시작합니다. Visual Studio의 실험적 인스턴스가 나타나면 도구 메뉴로 이동하여 TestAsyncCommand 호출 메뉴 항목을 찾습니다. 이 항목을 클릭하면 TextWriterService는 사용자가 지정한 파일에 씁니다. (솔루션을 열 필요는 없습니다. 명령을 호출하면 패키지도 로드되기 때문입니다.)