非同期タスクが悪いUXを作るとき
IDE必要に応じて拡張するCOMアドインを作成しています。多くの機能が関係していますが、この投稿のために2つに絞り込みましょう。
- ユーザーがモジュールとそのメンバーをナビゲートできるtreeviewを表示するCode Explorerツールウィンドウがあります。
- ユーザーがコードの問題をナビゲートして自動的に修正できるdatagridviewを表示するCode Inspectionsツールウィンドウがあります。
どちらのツールにも、開いているすべてのプロジェクトのすべてのコードを解析する非同期タスクを開始する「更新」ボタンがあります。 Code Explorerは解析結果を使用してtreeviewとCode Inspections解析結果を使用してコードの問題を見つけ、そのdatagridviewに結果を表示します。
ここで私がやろうとしていることは、解析結果を機能間で共有することです。これにより、Code Explorerが更新されたときに、Code Inspectionsはそれを認識しており、Code Explorerが行ったばかりの解析作業をやり直すことなく、自身をリフレッシュできます。
それで私がやったことは、パーサークラスを、機能が登録できるイベントプロバイダーにしたことです。
private void _parser_ParseCompleted(object sender, ParseCompletedEventArgs e)
{
Control.Invoke((MethodInvoker) delegate
{
Control.SolutionTree.Nodes.Clear();
foreach (var result in e.ParseResults)
{
var node = new TreeNode(result.Project.Name);
node.ImageKey = "Hourglass";
node.SelectedImageKey = node.ImageKey;
AddProjectNodes(result, node);
Control.SolutionTree.Nodes.Add(node);
}
Control.EnableRefresh();
});
}
private void _parser_ParseStarted(object sender, ParseStartedEventArgs e)
{
Control.Invoke((MethodInvoker) delegate
{
Control.EnableRefresh(false);
Control.SolutionTree.Nodes.Clear();
foreach (var name in e.ProjectNames)
{
var node = new TreeNode(name + " (parsing...)");
node.ImageKey = "Hourglass";
node.SelectedImageKey = node.ImageKey;
Control.SolutionTree.Nodes.Add(node);
}
});
}
そしてそれは機能します。私が抱えている問題は、それは...それが機能することです-つまり、コードインスペクションが更新されると、パーサーはコードエクスプローラー(および他のすべての人)に、 」 -そして解析が完了すると、パーサーはそのリスナーに「みんな、私はあなたのために新しい解析結果があります、あなたはそれについて何をしたいですか?」と伝えます。
これが作成する問題を説明するために例を紹介します。
- ユーザーがコードエクスプローラーを起動し、「ここで作業します」とユーザーに通知します。ユーザーがIDEで作業を続けると、コードエクスプローラーが再描画され、人生は美しいです。
- 次に、ユーザーはコード検査を起動し、「ここで私はここで作業している」と伝えます。パーサーはコードエクスプローラーに「おい、誰かの解析、それについて何をしたいのか?」と伝えます。 -コードエクスプローラーは、ユーザーに「お待ちください、私はここで働いています」と伝えます。ユーザーは引き続きIDEで作業できますが、コードエクスプローラーは更新されているため、ナビゲートできません。また、コード検査が完了するのを待っています。
- 対処したい検査結果にコードの問題が表示されます。ダブルクリックしてそこに移動し、コードに問題があることを確認して、[修正]ボタンをクリックします。モジュールが変更され、再解析する必要があるため、コード検査はそれを続行します。コードエクスプローラーは、ユーザーに「お待ちください、私はここで働いています」と伝えます。
これがどこへ向かっているのかわかりますか?気に入らないし、ユーザーも気に入らないだろう。何が欠けていますか?機能間で解析結果を共有するにはどうすればよいですか?それでも、機能が機能を実行するタイミングをユーザーが制御できるようにしますか?
私が尋ねている理由は、ユーザーが積極的に更新することを決定するまで実際の作業を延期し、解析結果が入ってくるときに解析結果を「キャッシュ」するとしたら、ツリービューを更新して古くなっている可能性のある解析結果でコードの問題を特定します...文字通り正方形に戻ります。各機能は独自の解析結果で機能します。機能間で解析結果を共有する方法はありますかおよび素敵なUXがありますか?
コードは c# ですが、コードを探しているのではなく、conceptsを探しています。
私がおそらくこれに取り組む方法は、完璧な結果を提供することに焦点を合わせるのではなく、ベストエフォートのアプローチに焦点を当てることです。これにより、少なくとも次の変更が行われます。
現在再解析を開始しているロジックを、開始ではなく要求に変換します。
再解析を要求するためのロジックは、次のようになる可能性があります。
IF parseIsRunning IS false startParsingThread() ELSE SET shouldParse TO true END
これは、次のようなパーサーをラップするロジックとペアになります。
SET parseIsRunning TO true DO SET shouldParse TO false doParsing() WHILE shouldParse IS true SET parseIsRunning TO false
重要なことは、最新の再解析要求が受け入れられるまでパーサーが実行されますが、同時に実行されるパーサーは1つだけであることです。
ParseStarted
コールバックを削除します。再解析の要求は、Fire and Forget操作になりました。または、ユーザーの操作を妨げないGUIの邪魔になる部分にリフレッシュインジケーターを表示する以外は何もしないように変換します。
古い結果の処理を最小限に抑えるようにしてください。
コードエクスプローラーの場合、ユーザーがナビゲートしたいメソッド、または正確な名前が見つからなかった場合は最も近いメソッドについて、妥当な数の行を上下に見るだけの簡単なものです。
コードインスペクタに何が適切かはわかりません。
実装の詳細はわかりませんが、全体的に、これはNetBeansエディタがこの動作を処理する方法と非常に似ています。現在更新中ですが、機能へのアクセスがブロックされることはありません。
失効した結果は、多くの場合十分です-特に結果がない場合と比較した場合。