提供非同步的 Visual Studio 服務
如果您想要取得服務而不封鎖 UI 執行緒,您應該建立非同步服務,並在背景執行緒上載入套件。 為了達到此目的,您可以使用 AsyncPackage (而非 Package),並使用非同步封裝的特殊非同步方法來新增服務。
如需提供同步 Visual Studio 服務的資訊,請參閱操作說明:提供服務。
實作非同步服務
建立 VSIX 專案 ([檔案]>[新增]>[專案]>[Visual C#]>[擴充性]>[VSIX 專案])。 將專案命名為 TestAsync。
將 VSPackage 新增至專案 選取 [解決方案總管] 中的專案節點,然後按一下 [新增]>[新增項目]>[Visual C# 項目]>[擴充性]>[Visual Studio 套件]。 將此檔案命名為 TestAsyncPackage.cs。
在 TestAsyncPackage.cs 中,將套件變更為繼承自
AsyncPackage
,而不是Package
:public sealed class TestAsyncPackage : AsyncPackage
若要實作服務,您需要建立三種類型:
識別服務的介面。 其中許多介面都是空的,也就是說,它們沒有方法,因為它們僅用於查詢服務。
描述服務介面的介面。 這個介面包含要實作的方法。
實作服務和服務介面的類別。
下列範例會示範三種類型中很基本的實作。 服務類別的建構函式必須設定服務提供者。 在此範例中,我們只會將服務新增至套件程式碼檔案。
將下列 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;
以下是非同步服務實作。 請注意,您必須在建構函式中設定非同步服務提供者,而不是同步服務提供者:
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
{. . . }
新增服務
在 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);
新增 Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll 的參考。
實作回呼方法做為非同步方法,以建立並傳回服務。
public async Task<object> CreateTextWriterService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) { TextWriterService service = new TextWriterService(this); await service.InitializeAsync(cancellationToken); return service; }
使用服務
現在您可以取得服務,並使用其方法。
我們將在初始設定式中顯示此專案,但您可以在您想要使用服務的任何位置取得服務。
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
變更為您電腦上有意義的檔案名稱和路徑!建置並執行程式碼。 當 Visual Studio 的實驗執行個體出現時,開啟解決方案。 這會導致
AsyncPackage
自動載入。 當初始設定式執行時,您應該會在您指定的位置中找到檔案。
在命令處理常式中使用非同步服務
以下是如何在功能表命令中使用非同步服務的範例。 您可以使用這裡所示的程序,在其他非同步方法中使用服務。
將功能表命令新增至您的專案。 (在 [解決方案總管] 中,選取專案節點,按一下滑鼠右鍵,然後選取 [新增]>[新增項目]>[擴充性]>[自訂命令]。) 為 TestAsyncCommand.cs 命令檔案命名。
自訂命令範本會將
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); }
刪除
Initialize()
方法。在 TestAsyncCommand.cs 檔案中,尋找
MenuItemCallback()
方法。 刪除方法的主體。新增 using 指示詞:
using System.IO;
新增名為
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"); }
從
MenuItemCallback()
方法呼叫這個方法:private void MenuItemCallback(object sender, EventArgs e) { UseTextWriterAsync(); }
組建方案並開始偵錯。 當 Visual Studio 的實驗執行個體出現時,請移至 [工具] 功能表,然後尋找 [叫用 TestAsyncCommand] 功能表項目。 當您按一下它時,TextWriterService 會寫入您指定的檔案。 (您不需要開啟解決方案,因為叫用命令也會使套件載入。)