我有一个类,它在远离我的WPF MVVM视图模型的后台线程上抽象执行长时间运行的方法。我也有这个类接口和IoC注入到我的大多数视图模型中。
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
});
}
这是简化的,因为我也通过一个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 :(得分:2)
我相信这里的问题确实存在(正如你所说的)Status
property的检查。
TaskStatus
enumeration表示Task
实例不仅具有运行/未运行的二进制状态,而是可以处于多个状态。
创建Task
后,根据TaskScheduler
,Task
会将Running
置于 Created
州之前的以下状态 :
WaitingForActivation
- 任务已初始化但尚未安排。WaitingToRun
- 任务正在等待由.NET Framework基础架构在内部激活和调度。TaskStatus
- 任务已安排执行但尚未开始执行。这样,您对Running
Task
的检查可能会失败,因为它处于以上某种状态。
我建议您只是检查null
的参考;如果是Task
,则创建一个新的SaveChanges
,否则只返回。
这里的假设是对Task
的调用意味着在对象上调用一次(或者在保存完成之前不做任何事情)。
如果您打算再次调用该方法(可能是因为已经进行了其他更改),您应该在Task
上继续设置null
到{{{{}}的引用1}}操作完成时。这样,当第二次调用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;
});
}
}