非同期の Visual Studio サービスを提供する
UI スレッドをブロックせずにサービスを取得する場合は、非同期サービスを作成し、バックグラウンド スレッドでパッケージを読み込む必要があります。 この目的で、Package ではなく AsyncPackage を使用して、非同期パッケージの特別な非同期メソッドを使用してサービスを追加できます。
Visual Studio の同期サービスの提供の詳細については、「方法: サービスを提供する」を参照してください。
非同期サービスを実装する
VSIX プロジェクトを作成します ([ファイル]>[新規作成]>[プロジェクト]>[Visual C#]>[機能拡張]>[VSIX プロジェクト])。 プロジェクトに TestAsync という名前を付けます。
VSPackage をプロジェクトに追加します。 ソリューション エクスプローラーでプロジェクト ノードを選択し、[追加]>[新しい項目]>[Visual C# アイテム]>[機能拡張]>[Visual Studio パッケージ] の順にクリックします。 このファイルに TestAsyncPackage.cs という名前を指定します。
TestAsyncPackage.cs で、パッケージを
Package
ではなくAsyncPackage
から継承するように変更します。public sealed class TestAsyncPackage : AsyncPackage
サービスを実装するには、次の 3 種類を作成する必要があります。
サービスを識別するインターフェイス。 これらのインターフェイスの多くは空です。つまり、サービスのクエリ専用に使用されるため、メソッドはありません。
サービス インターフェイスを記述するインターフェイス。 このインターフェイスには、実装するメソッドが含まれています。
サービスとサービス インターフェイスの両方を実装するクラス。
次の例に、3 つの種類のきわめて基本的な実装を示します。 サービス クラスのコンストラクターでは、サービス プロバイダーを設定する必要があります。 この例では、パッケージ コード ファイルにサービスを追加するだけです。
パッケージ ファイルに次の 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); }
このサービスがこのパッケージの外部で表示されるようにするには、最後のパラメーターとして、promote フラグの値を 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 によって、指定したファイルに書き込まれます。 (コマンドを呼び出すとパッケージも読み込まれるため、ソリューションを開く必要はありません)。