VSIXプロジェクトにテンプレートカスタムコマンドを追加すると、VisualStudioが生成するスキャフォールディングコードには次の一般的な構造が含まれます。
/// <summary>
/// Initializes a new instance of the <see cref="GenerateConfigSetterCommand"/> class.
/// Adds our command handlers for menu (commands must exist in the command table file)
/// </summary>
/// <param name="package">Owner package, not null.</param>
/// <param name="commandService">Command service to add command to, not null.</param>
private GenerateConfigSetterCommand(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
/// <summary>
/// This function is the callback used to execute the command when the menu item is clicked.
/// See the constructor to see how the menu item is associated with this function using
/// OleMenuCommandService service and MenuCommand class.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
private void Execute(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
// TODO: Command implementation goes here
}
/// <summary>
/// Initializes the singleton instance of the command.
/// </summary>
/// <param name="package">Owner package, not null.</param>
public static async Task InitializeAsync(AsyncPackage package)
{
// Switch to the main thread - the call to AddCommand in GenerateConfigSetterCommand's constructor requires
// the UI thread.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService;
Instance = new GenerateConfigSetterCommand(package, commandService);
}
フレームワークが提供するMenuCommand
クラスは、シグニチャを持つ標準の同期イベント処理デリゲートを使用することに注意してくださいvoid Execute(object sender, EventArgs e)
。また、の存在から判断するThreadHelper.ThrowIfNotOnUIThread()
と、Execute
メソッドの本体が実際にUIスレッドで実行されることはかなり明らかです。つまり、カスタムコマンドの本体でブロッキング同期操作を実行することはお勧めできません。または実行何も非常に長いその実行()ハンドラの本体で実行されているが。
そのasync/await
ため、カスタムコマンド実装で実行時間の長い操作をUIスレッドから切り離すために使用したいのですが、それをVSIXMPFフレームワークのスキャフォールディングに正しく適合させる方法がわかりません。
Executeメソッドの署名をに変更するとasync void Execute(...)
、VSはThreadHelper.ThrowIfNotOnUIThread()
呼び出しに問題があることを通知します。
「代わりに必要なスレッドに切り替える」方法がわかりません。それはメソッドのawait ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken)
コードInitializeAsync
が行っていることですか?それをコピーするだけでいいですか?
例外処理はどうですか?同期void Execute()
ハンドラーが例外をスローすることを許可すると、VSはそれをキャッチし、一般的なエラーメッセージボックスを表示します。しかし、これを変更するとasync void Execute()
、を呼び出したスレッドでキャッチされない例外が発生せずExecute
、他の場所でより深刻な問題が発生する可能性があります。ここで行う正しいことは何ですか?Task.Result
正しいコンテキストで例外を再スローするために同期的にアクセスすることは、よく知られているデッドロックの標準的な例のようです。実装内のすべての例外をキャッチし、より適切に処理できないものについては、独自の汎用メッセージボックスを表示する必要がありますか?
偽の同期カスタムコマンドの実装は次のとおりです。
internal sealed class GenerateConfigSetterCommand
{
[...snip the rest of the class...]
/// <summary>
/// This function is the callback used to execute the command when the menu item is clicked.
/// See the constructor to see how the menu item is associated with this function using
/// OleMenuCommandService service and MenuCommand class.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
private void Execute(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
// Command implementation goes here
WidgetFrobulator.DoIt();
}
}
class WidgetFrobulator
{
public static void DoIt()
{
Thread.Sleep(1000);
throw new NotImplementedException("Synchronous exception");
}
public static async Task DoItAsync()
{
await Task.Delay(1000);
throw new NotImplementedException("Asynchronous exception");
}
}
カスタムコマンドボタンがクリックされると、VSにはいくつかの基本的なエラー処理があり、簡単なメッセージボックスが表示されます。
[OK]をクリックすると、メッセージボックスが閉じ、VSは「バグのある」カスタムコマンドに邪魔されることなく動作を続けます。
ここで、カスタムコマンドのExecuteイベントハンドラーをナイーブな非同期実装に変更するとします。
private async void Execute(object sender, EventArgs e)
{
// Cargo cult attempt to ensure that the continuation runs on the correct thread, copied from the scaffolding code's InitializeAsync() method.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
// Command implementation goes here
await WidgetFrobulator.DoItAsync();
}
ここで、コマンドボタンをクリックすると、未処理の例外が原因でVisualStudioが終了します。
私の質問は次のとおりです。非同期VSIXカスタムコマンドの実装から発生する例外を処理するためのベストプラクティスの方法は何ですか。これにより、VSは、メインのデッドロックのリスクを冒すことなく、同期コードで未処理の例外を処理するのと同じ方法で、非同期コードで未処理の例外を処理します。糸?
ThreadHelper.JoinableTaskFactory
APIの正しい使用法を説明するドキュメントはこちらです。
最後に、私は次のことを行いました。
private async void Execute(object sender, EventArgs e)
{
try
{
await CommandBody();
}
catch (Exception ex)
{
// Generic last-chance MessageBox display
// to ensure the async exception can't kill Visual Studio.
// Note that software for end-users (as opposed to internal tools)
// should usually log these details instead of displaying them directly to the user.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
VsShellUtilities.ShowMessageBox(
this._package,
ex.ToString(),
"Command failed",
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
}
private async Task CommandBody()
{
// Actual implementation logic in here
}
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加