共用方式為


提供非同步的 Visual Studio 服務

如果您想要取得服務而不封鎖 UI 執行緒,您應該建立非同步服務,並在背景執行緒上載入套件。 為了達到此目的,您可以使用 AsyncPackage (而非 Package),並使用非同步封裝的特殊非同步方法來新增服務。

如需提供同步 Visual Studio 服務的資訊,請參閱操作說明:提供服務

實作非同步服務

  1. 建立 VSIX 專案 ([檔案]>[新增]>[專案]>[Visual C#]>[擴充性]>[VSIX 專案])。 將專案命名為 TestAsync

  2. 將 VSPackage 新增至專案 選取 [解決方案總管] 中的專案節點,然後按一下 [新增]>[新增項目]>[Visual C# 項目]>[擴充性]>[Visual Studio 套件]。 將此檔案命名為 TestAsyncPackage.cs

  3. TestAsyncPackage.cs 中,將套件變更為繼承自 AsyncPackage,而不是 Package

    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 的詳細資訊,請參閱註冊和取消註冊 VSPackage

  • 您必須將 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 會寫入您指定的檔案。 (您不需要開啟解決方案,因為叫用命令也會使套件載入。)