2012-02-28 3 views
2

私は、WPF MVVMビューモデルからバックグラウンドスレッドで長い実行メソッドを抽象化するクラスを持っています。私はまた、このクラスをインターフェイスし、ほとんどのビューモデルにIoCを注入しました。.NETタスク/ TPLテストと嘲笑? (または間違った使用ですか?)

public interface IAsyncActionManager : INotifyPropertyChanged 
{   
    /// <summary> 
    /// Sets and gets the IsBusy property. 
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary> 
    bool IsBusy { get; } 

    Task StartAsyncTask(Action backgroundAction); 
} 

マイビューモデルのようなさまざまな方法でこのクラスを使用します。

private void LoadStuff() 
{ 
    ActionManager.StartAsyncTask(() => { // Load stuff from database here }); 
} 

そして、私のXAMLのいくつかで、私はIsBusyプロパティに直接バインド:とにかく

<Grid Cursor="{Binding ActionManager.IsBusy, Converter={Converters:BusyMouseConverter}}"> 

- 今あなたは、バックグラウンドを持って、私は今、もう少し派手な何かをしようとしている:

private Task _saveChangesTask; 
public void SaveChanges() 
{ 
    if (_saveChangesTask != null && _saveChangesTask.Status != TaskStatus.Running) 
     return; 

    _saveChangesTask = ActionManager.StartAsyncTask(() => 
    { 
     // Save stuff here - slowly 
    }); 
} 

私もそれはWPFがCanExecuteなどとのビューで使用していますが、保存するアクションが二度実行されないように、タスクのこの「キャッシュ」であるCommandオブジェクトを経由してフックアップ持っているとして、これは簡略化されています。

は今、問題になって、私はこのロジックユニットテストしたい - どのように私はそうするのですか? テストでTaskCompletionSourceを使用しようとしましたが、タスクを「実行中」の状態にできません...?

var tcs = new TaskCompletionSource<object>(); 
// tcs.Task.Status is now WaitingForActivation 

// tcs.Task.Start(Synchronous.TaskScheduler); // Doesn't work - throws an Exception. 

A.CallTo(() => mockAsyncActionManager.StartAsyncTask(A<Action>._, A<Action<Task>>._)).Returns(tcs.Task); 

誰でも手がかりを得ましたか?これはできますか?

私は私が間違ってTPLを使用しているという考え持っている - 私は、タスクのステータスに頼るべきではないことを - しかし、(提案は歓迎します)別の方法で同様のことを達成する方法がわからないし。

乾杯、

+0

あなたのセーブ・タスクにはマイナーな競合状態があります。あなたは小切手と割り当ての周りにロックを配置する必要があります。技術的には、2つのスレッドがチェックし、ヌルを取得してからタスクを2回実行しますが、1つだけ割り当てます(2番目のタスクは最初のタスクを上書きします)。 – casperOne

答えて

2

私はここでの問題は、(あなたが言ったように)Status propertyのチェックとうそないと信じています。

TaskStatus enumerationは、Taskのインスタンスが実行/実行されていないバイナリ状態を持っているのではなく、複数の状態になる可能性があることを示しています。

Taskが作成された

TaskSchedulerに応じて、それが次の状態でTaskを置くRunning状態:

  • Created - タスクが初期化されていますが、まだ予定されていません。
  • WaitingForActivation - タスクは、.NET Frameworkインフラストラクチャによって内部的にアクティブ化され、スケジュールされるのを待機しています。
  • WaitingToRun - タスクが実行のためにスケジュールされているが、まだ実行を開始されていません。それは上記のいずれかの状態にあるので、それと

RunningTaskStatusに対するあなたのチェックが失敗する可能性があります。

私がお勧めすることは、単にTaskの基準と照合してチェックです。 nullの場合は、新しいTaskを作成します。それ以外の場合は、ただ返します。ここ

仮定はSaveChangesへの呼び出しを対象に一度と呼ばれる(または保存が完了するまでは何もしない)ことを意味しているということです。

あなたは(他の変更が行われているため、おそらく)再びメソッドを呼び出ししようとしている場合は、操作が完了したときにnullTaskへの参照を設定しますTaskの継続を持っている必要があります。このようにして、SaveChangesを2回目に呼び出すと、参照との比較が成功します。

サイドノートでは、I've pointed out in the comments that you have a race conditionです。 Taskへの参照をnullの続きに設定する場合は、スレッドセーフな方法でチェックと割り当てを実行する必要があります(継続は別のスレッドで実行されるため)。

private Task _saveChangesTask; 

// Used to synchronize access to _saveChangesTask 
private readonly object _saveChangesTaskLock = new object(); 

public void SaveChanges() 
{ 
    // Guard access to the reference. 
    lock (_saveChangesTaskLock) 
    { 
     // Check and assign. 
     if (_saveChangesTask != null) return; 

     _saveChangesTask = ActionManager.StartAsyncTask(() => 
     { 
      // Save stuff here - slowly 

      // Done saving stuff here - slowly 
      // (BTW, is the above a reference from "True Lies"?) 
      // Remove reference to task. This is on another thread 
      // so using a lock again is ok. 
      // Guard access to the reference. 
      lock (_saveChangesTaskLock) _saveChangesTask = null; 
     }); 
    } 
} 
関連する問題