2012-02-28 35 views
2

我有一个类抽象执行远离我的WPF MVVM视图模型后台线程的长时间运行方法。我也有这个类接口和IoC注入我的大多数视图模型。.NET任务/ TPL测试和嘲讽? (或不正确的用法?)

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

而且在我的一些XAML我直接绑定到IsBusy属性:

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); 
} 

我的视图模型以各种方式,如使用该类

<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 
    }); 
} 

这是简化的,因为我也通过一个Command对象把它连接起来,WPF在它的视图中使用了CanExecute等,但是这个任务的“缓存”使得保存操作不会运行两次。

现在,解决问题,我想单元测试这个逻辑 - 我该怎么做? 我试过在我的测试中使用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

您的保存任务有轻微的竞争条件。您应该在支票和作业周围锁定一个锁。从技术上讲,两个线程可以检查,得到空值,然后运行任务两次,但只分配一个(第二个任务将覆盖第一个任务)。 – casperOne 2012-02-28 17:54:51

回答

2

我相信这里的问题确实谎言(如你所说)与Status property的检查。

TaskStatus enumeration指示Task实例不仅具有正在运行/未运行的二进制状态,而是可以处于许多状态。

当创建一个Task,取决于TaskScheduler,它会把Task以下状态Running状态:

  • Created - 任务已初始化但尚未排定。
  • WaitingForActivation - 该任务正在等待由.NET Framework基础结构在内部激活和调度。
  • WaitingToRun - 该任务已计划执行,但尚未开始执行。

就这样,你对的RunningTaskStatus检查可能会失败,因为它是在上述状态之一。

我推荐的是你只需检查Task的参考;如果是null,则创建一个新的Task,否则,只需返回。

的这里的假设是,到SaveChanges呼叫是指物体上被称为一次(或直到保存后不能做任何事情)。

如果你要再次调用该方法(可能,因为其他修改过),你应该对Task的延续将在参考Task设置null当操作完成。这样,当SaveChanges被第二次调用时,对照参考的检查将成功。

请注意,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; 
     }); 
    } 
}