1

MVVM に準拠した WPF アプリケーションがあります。

UI をレスポンシブにするために、TPL を使用して実行時間の長いコマンドを実行し、BusyIndi​​catorを使用してユーザーに表示しています。そのアプリケーションは現在ビジーです。

ビューモデルの1つに、次のコマンドがあります。

public ICommand RefreshOrdersCommand { get; private set; }

public OrdersEditorVM()
{
    this.Orders = new ObservableCollection<OrderVM>();
    this.RefreshOrdersCommand = new RelayCommand(HandleRefreshOrders);

    HandleRefreshOrders();
}

private void HandleRefreshOrders()
{
    var task = Task.Factory.StartNew(() => RefreshOrders());
    task.ContinueWith(t => RefreshOrdersCompleted(t.Result), 
        CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
    task.ContinueWith(t => this.LogAggregateException(t.Exception, Resources.OrdersEditorVM_OrdersLoading, Resources.OrdersEditorVM_OrdersLoadingFaulted),
        CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}

private Order[] RefreshOrders()
{
    IsBusy = true;
    System.Diagnostics.Debug.WriteLine("Start refresh.");
    try
    {
        var orders = // building a query with Entity Framework DB Context API

        return orders
            .ToArray();
    }
    finally
    {
        IsBusy = false;
        System.Diagnostics.Debug.WriteLine("Stop refresh.");
    }
}

private void RefreshOrdersCompleted(Order[] orders)
{
    Orders.RelpaceContent(orders.Select(o => new OrderVM(this, o)));

    if (Orders.Count > 0)
    {
        Orders[0].IsSelected = true;
    }
}

IsBusyプロパティは にバインドされBusyIndicator.IsBusyRefreshOrdersCommandプロパティはツールバーのボタンにバインドされ、BusyIndicatorこのビュー モデルのビュー内に配置されます。

問題

ユーザーがボタンをクリックする頻度が低い場合、すべて正常に機能します。BusyIndicatorボタンでツールバーを非表示にし、データが読み込まれ、BusyIndicator消えます。出力ウィンドウに、次の行のペアが表示されます。

リフレッシュ開始。リフレッシュを停止します。

ただし、ユーザーがボタンを頻繁にクリックBusyIndicatorすると、ツールバーが時間内に非表示にならず、2 つのバックグラウンド スレッドがRefreshOrdersメソッドを実行しようとし、例外が発生します (EFDbContextはスレッドセーフではないため、問題ありません)。出力ウィンドウに次の図が表示されます。

リフレッシュ開始。リフレッシュ開始。

私は何を間違っていますか?

BusyIndicatorのコードを調べました。何が間違っているのかわかりません: を設定IsBusyすると が 2 回呼び出されるだけで、のコンテンツVisualStateManager.GoToStateが非表示になる四角形が表示されます。BusyIndicator

                    <VisualState x:Name="Visible">
                       <Storyboard>
                          <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">
                             <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                <DiscreteObjectKeyFrame.Value>
                                   <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                             </DiscreteObjectKeyFrame>
                          </ObjectAnimationUsingKeyFrames>
                          <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">
                             <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                <DiscreteObjectKeyFrame.Value>
                                   <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                             </DiscreteObjectKeyFrame>
                          </ObjectAnimationUsingKeyFrames>
                       </Storyboard>
                    </VisualState>

...そしてコンテンツを無効にします:

                    <VisualState x:Name="Busy">
                       <Storyboard>
                          <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">
                             <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                <DiscreteObjectKeyFrame.Value>
                                   <sys:Boolean>False</sys:Boolean>
                                </DiscreteObjectKeyFrame.Value>
                             </DiscreteObjectKeyFrame>
                          </ObjectAnimationUsingKeyFrames>
                       </Storyboard>
                    </VisualState>

何か案は?

更新します。問題は、コマンドの再入を防ぐ方法ではありません。ボタンを2回押すことができるメカニズムについて疑問に思っています。

これがどのように機能するかです(私の観点から):

  • ViewModel.IsBusyでバインドされていBusyIndicator.IsBusyます。バインディングは同期的です。
  • BusyIndicator.IsBusy呼び出しのセッターはVisualStateManager.GoToState2 回。BusyIndicatorこれらの呼び出しの 1 つで、のコンテンツ (私の場合はボタン)と重なる四角形が表示されます。
  • したがって、私が理解しているように、 を設定した後、ボタンを物理的に達成することはできません。これはViewModel.IsBusy、これらすべてが同じ (UI) スレッドで発生するためです。

しかし、どのようにしてボタンが 2 回押されるのでしょうか??

4

1 に答える 1

3

有効なプログラム フローを制御するためにビジー インジケーターに依存しようとはしません。コマンドの実行が設定され、それ自体が既にIsBusy実行されている場合は実行されません。IsBusyTrue

private void HandleRefreshOrders()
{
    if (IsBusy)
        return;
...

Enabledボタンの状態をバインドすることもできますがIsBusy、中心的な解決策は、意図しない再入力からコマンドを保護することだと思います。ワーカーをキックオフすると、複雑さも増します。ステータスの設定をに移動し、実装HandleRefreshOrdersで状態のリセットを処理します。ContinueWith(スレッドセーフであることを確認するには、追加のアドバイス/読み取りが必要になる場合があります。)

*編集:IsBusy二重実行を避けるために移動を明確にするために:

private void HandleRefreshOrders()
{
    if (IsBusy)
        return;

    IsBusy = true;

    var task = Task.Factory.StartNew(() => RefreshOrders());
    task.ContinueWith(t => 
            {
                RefreshOrdersCompleted(t.Result);
                IsBusy = false;
            }, 
        CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,  TaskScheduler.FromCurrentSynchronizationContext());
    task.ContinueWith(t => 
            {
                this.LogAggregateException(t.Exception, Resources.OrdersEditorVM_OrdersLoading, Resources.OrdersEditorVM_OrdersLoadingFaulted);
                IsBusy = false;
            },
    CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}

次に、メソッドIsBusyから参照を削除します。RefreshOrdersボタンを1回クリックするIsBusyと、設定されます。ユーザーがすばやくクリックすると、コマンドをトリガーしようとする 2 回目の試行はスキップされます。タスクはバックグラウンド スレッドで実行され、完了するとIsBusyフラグがリセットされ、コマンドが再び応答します。RefreshOrdersCompletedこれは現在、呼び出しが例外をバブルしLogAggregateExceptionないことを前提としています。それ以外の場合、例外がスローされた場合、フラグはリセットされません。これらの呼び出しの前にフラグ リセットを移動するか、アノン内で try/finally を使用できます。メソッド宣言。

于 2012-12-21T12:39:02.673 に答える