web-dev-qa-db-ja.com

C#5非同期リエントラントのソリューション

だから、C#5の新しい非同期サポートについて何かが私を悩ませています:

ユーザーは、非同期操作を開始するボタンを押します。呼び出しはすぐに戻り、メッセージポンプが再び実行を開始します。これがポイントです。

したがって、ユーザーはボタンをもう一度押すことができます-再入可能性を引き起こします。これが問題である場合はどうなりますか?

私が見たデモでは、awaitを呼び出す前にボタンを無効にし、後で再度有効にします。これは、実際のアプリでは非常に壊れやすいソリューションのように思えます。

特定の実行中の操作のセットに対して無効にする必要があるコントロールを指定する、ある種のステートマシンをコーディングする必要がありますか?それとももっと良い方法がありますか?

操作中はモーダルダイアログを表示したくなりますが、これはハンマーを使用するような感じです。

誰か良いアイデアを思いついてください。


編集:

操作の実行中に使用してはならないコントロールを無効にすると、ウィンドウに多数のコントロールが含まれているとすぐに複雑になるので、壊れやすいと思います。最初のコーディング時とその後のメンテナンス時の両方で、バグの可能性を減らすため、物事を単純にしておくのが好きです。

特定の操作で無効にする必要があるコントロールのコレクションがある場合はどうなりますか?また、複数の操作が同時に実行されている場合はどうなりますか?

16
Nicholas Butler

だから、C#5の新しい非同期サポートについて何かが私を悩ませています:ユーザーが非同期操作を開始するボタンを押します。呼び出しはすぐに戻り、メッセージポンプが再び実行を開始します。これがポイントです。したがって、ユーザーはボタンをもう一度押すことができます-再入可能性を引き起こします。これが問題である場合はどうなりますか?

言語で非同期サポートがなくても、これがalreadyの問題であることから始めましょう。イベントの処理中には、多くのことが原因でメッセージがデキューされる可能性があります。あなたはすでにこの状況に対して防御的にコーディングする必要があります。新しい言語機能は、それをより明白にするだけです。これは良いことです。

不要な再入を引き起こすイベント中に実行されるメッセージループの最も一般的な原因はDoEventsです。 awaitとは対照的にDoEventsの良い点は、awaitにより、新しいタスクが現在実行中のタスクを「枯渇」させる可能性が低くなることです。 DoEventsはメッセージループをポンピングしてから、再入可能イベントハンドラーを同期的に呼び出しますが、awaitは通常enqueues新しいタスクなので、未来。

私が見たデモでは、彼らは待機コールの前にボタンを無効にし、その後再び有効にします。操作の実行中に使用してはならないコントロールを無効にすると、ウィンドウに多数のコントロールが含まれているとすぐに複雑になるので、壊れやすいと思います。特定の操作で無効にする必要があるコントロールのコレクションがある場合はどうなりますか?また、複数の操作が同時に実行されている場合はどうなりますか?

OO言語で他の複雑さを管理するのと同じ方法でその複雑さを管理できます:mechanismsをクラスに抽象化し、クラスに責任を持たせますpolicyを正しく実装するため。(メカニズムをポリシーと論理的に区別するようにしてください。メカニズムを過度にいじる必要なく、ポリシーを変更することができるはずです。)

興味深い方法で相互作用する多くのコントロールを備えたフォームがある場合、あなたはあなた自身の作成の複雑さの世界に住んでいます。その複雑さを管理するためのコードを書く必要があります。それが気に入らない場合はフォームを単純化して、それほど複雑にならないようにします。

操作中はモーダルダイアログを表示したくなりますが、これはハンマーを使用するような感じです。

ユーザーはそれを嫌います。

14
Eric Lippert

awaitの前にボタンを無効にし、通話が完了したときに再度有効にするのはなぜ難しいと思いますか?ボタンが再度有効にならないのではないかと心配しているからですか?

まあ、呼び出しが返らない場合は、再度呼び出すことはできないため、これが望ましい動作のようです。これは、修正する必要があるエラー状態を示します。非同期呼び出しがタイムアウトになった場合でも、アプリケーションが不確定な状態のままになる可能性があります。この場合も、ボタンを有効にすると危険な場合があります。

ボタンの状態に影響を与えるいくつかの操作がある場合にのみ、これが厄介になるかもしれませんが、それらの状況は非常にまれであると思います。

6
ChrisF

私は通常WPFを使用しているため、これが当てはまるかどうかはわかりませんが、ViewModelを参照している可能性があるので、そうかもしれません。

ボタンを無効にする代わりに、IsLoadingフラグをtrueに設定します。次に、無効にするUI要素をフラグにバインドできます。その結果、ビジネスロジックではなく、独自の有効な状態を整理することがUIの仕事になります。

それに加えて、WPFのほとんどのボタンはICommandにバインドされており、通常はICommand.CanExecute に等しい !IsLoadingなので、IsLoadingがtrueに等しいときにコマンドが自動的に実行されないようにします(ボタンも無効になります)。

5
Rachel

私が見たデモでは、彼らは待機コールの前にボタンを無効にし、その後再び有効にします。これは、実際のアプリでは非常に壊れやすいソリューションのように思えます。

これについて脆​​弱なことは何もありません。それはユーザーが期待することです。ユーザーがボタンを押す前に待機する必要がある場合は、待機させます。

特定のコントロールシナリオで、一度にどのコントロールを無効化/有効化するかを決定することがかなり複雑になることはわかりますが、複雑なUIの管理が複雑になるとは思いませんか?恐ろしい側面が多すぎる場合特定のコントロールからの効果、フォーム全体をいつでも無効にして、全体に「ローディング」をギグしながら投げることができます。これがすべての単一のコントロールに当てはまる場合、UIは最初はあまりうまく設計されていない可能性があります(密結合、不十分なコントロールのグループ化など)。

3

ウィジェットを無効にすることは、最も複雑でエラーが発生しやすいオプションです。非同期操作を開始する前にハンドラーで無効にし、操作の最後に再度有効にします。したがって、各コントロールのdisable + enableは、そのコントロールの処理に対してローカルに保たれます。

デリゲートとコントロール(またはそれ以上)を使用するラッパーを作成し、コントロールを無効にして非同期で何かを実行し、デリゲートを実行してからコントロールを再度有効にすることもできます。例外を処理する必要があります(最後にenableを実行します)。そのため、操作が失敗した場合でも確実に機能します。そして、ステータスバーにメッセージを追加することと組み合わせて、ユーザーが何を待っているのかを知ることができます。

いくつかのロジックを追加して無効化をカウントすることもできます。これにより、2つの操作がある場合、システムは引き続き機能します。これにより、3つ目の操作が両方とも無効になりますが、相互に排他的ではありません。コントロールを2回無効にすると、再び2回有効にした場合にのみ再び有効になります。それはあなたが得ることができる限りケースを分離させながらほとんどのケースをカバーするはずです。

一方、ステートマシンのようなものはすべて複雑になります。私の経験では、これまでに見たすべてのステートマシンを維持することは困難でした。また、ステートマシンはダイアログ全体を網羅し、すべてをすべてに結び付ける必要があります。

2
Jan Hudec

Jan HudecとRachelは関連するアイデアを表明しましたが、Model-Viewアーキテクチャのようなものを使用することをお勧めします-オブジェクトでフォームの状態を表現し、フォーム要素をその状態に結び付けます。これにより、ボタンを無効にして、より複雑なシナリオに対応できるようにすることができます。

1
psr