VSIXでのカスタムコマンドの非同期実装

Hydrargyrum

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は、メインのデッドロックのリスクを冒すことなく同期コードで未処理の例外を処理するのと同じ方法で非同期コードで未処理の例外を処理します。糸?

Hydrargyrum

ThreadHelper.JoinableTaskFactoryAPIの正しい使用法を説明するドキュメントこちらです。

最後に、私は次のことを行いました。

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]

編集
0

コメントを追加

0

関連記事

分類Dev

カスタムWPFコマンドの実装

分類Dev

非同期タスク内でのさまざまなコマンドの実行

分類Dev

LaravelElixirコマンドの非同期実行

分類Dev

カスタム非同期タスクの実装が機能しない

分類Dev

MVVMライトでの非同期コマンドの実行

分類Dev

カスタムデコレータでのトルネード非同期アクション

分類Dev

Aureliaカスタムコンポーネントの添付メソッドで非同期作業を行う方法

分類Dev

同期コードベースで非同期パフォーマンスの利点を実現

分類Dev

カスタムPython非同期イベントループを実装する方法をどのように学ぶことができますか?

分類Dev

どのコードが実際に非同期/待機パターンで「マルチスレッド」で実行されますか?

分類Dev

非同期の問題、.thenの後にshelljsコマンドを実行できない

分類Dev

同期および非同期実装のコードの複製

分類Dev

カスタムコマンドラインと実行を実装する方法

分類Dev

カスタムレンダラーのOnElementChangedメソッド内で非同期メソッドを実行する方法

分類Dev

TotalCommanderのカスタムコマンド

分類Dev

ZSHのカスタムコマンド

分類Dev

非同期コマンドを実装する方法

分類Dev

カスタムバインドの問題の実装

分類Dev

非同期再試行操作でのタイムアウトの実装

分類Dev

コマンドフォルダ内のコマンドファイルを非同期で実行する方法

分類Dev

非同期リクエストでのフォーム認証の実装

分類Dev

handle()の後にカスタム職人コマンドでコードを実行する

分類Dev

MvvmCrossコマンド内の非同期タスクが返されない

分類Dev

コンストラクターでの非同期関数のバインド

分類Dev

Vertxの非同期カスタムメソッド

分類Dev

VisualStudioでカスタムファイル固有のコマンド/タスクを実行する方法は?

分類Dev

時々非同期である操作のためのインターフェースを作成して実装する方法

分類Dev

リストADTのパターンマッチングカスタム実装

分類Dev

イベントベースの非同期パターンの実装は、常に現在の同期コンテキストをキャプチャしますか?

Related 関連記事

  1. 1

    カスタムWPFコマンドの実装

  2. 2

    非同期タスク内でのさまざまなコマンドの実行

  3. 3

    LaravelElixirコマンドの非同期実行

  4. 4

    カスタム非同期タスクの実装が機能しない

  5. 5

    MVVMライトでの非同期コマンドの実行

  6. 6

    カスタムデコレータでのトルネード非同期アクション

  7. 7

    Aureliaカスタムコンポーネントの添付メソッドで非同期作業を行う方法

  8. 8

    同期コードベースで非同期パフォーマンスの利点を実現

  9. 9

    カスタムPython非同期イベントループを実装する方法をどのように学ぶことができますか?

  10. 10

    どのコードが実際に非同期/待機パターンで「マルチスレッド」で実行されますか?

  11. 11

    非同期の問題、.thenの後にshelljsコマンドを実行できない

  12. 12

    同期および非同期実装のコードの複製

  13. 13

    カスタムコマンドラインと実行を実装する方法

  14. 14

    カスタムレンダラーのOnElementChangedメソッド内で非同期メソッドを実行する方法

  15. 15

    TotalCommanderのカスタムコマンド

  16. 16

    ZSHのカスタムコマンド

  17. 17

    非同期コマンドを実装する方法

  18. 18

    カスタムバインドの問題の実装

  19. 19

    非同期再試行操作でのタイムアウトの実装

  20. 20

    コマンドフォルダ内のコマンドファイルを非同期で実行する方法

  21. 21

    非同期リクエストでのフォーム認証の実装

  22. 22

    handle()の後にカスタム職人コマンドでコードを実行する

  23. 23

    MvvmCrossコマンド内の非同期タスクが返されない

  24. 24

    コンストラクターでの非同期関数のバインド

  25. 25

    Vertxの非同期カスタムメソッド

  26. 26

    VisualStudioでカスタムファイル固有のコマンド/タスクを実行する方法は?

  27. 27

    時々非同期である操作のためのインターフェースを作成して実装する方法

  28. 28

    リストADTのパターンマッチングカスタム実装

  29. 29

    イベントベースの非同期パターンの実装は、常に現在の同期コンテキストをキャプチャしますか?

ホットタグ

アーカイブ