我有一个类,它从我的 WPF MVVM View 模型中抽象出在后台线程上执行长时间运行的方法。我还将此类接口(interface)和 IoC 注入(inject)到我的大多数 View 模型中。
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);
}
我的 View 模型以多种方式使用此类,例如:
private void LoadStuff()
{
ActionManager.StartAsyncTask(() => { // Load stuff from database here });
}
在我的一些 XAML 中,我直接绑定(bind)到 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 在其 View 中使用 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 - 我不应该依赖任务状态 - 但不确定如何以另一种方式实现类似的事情(欢迎提出建议)。
干杯,
最佳答案
我相信这里的问题确实在于(正如你所说的)Status
property 的检查。 .
TaskStatus
enumeration表示 Task
实例不仅具有运行/未运行的二进制状态,而且可以处于多种状态。
创建任务
时,取决于 TaskScheduler
,它会将Task
置于Running
状态之前以下状态:
已创建
- 任务已初始化,但尚未安排。WaitingForActivation
- 任务正在等待 .NET Framework 基础结构在内部激活和调度。WaitingToRun
- 任务已计划执行,但尚未开始执行。
这样,您对 Running
的 TaskStatus
的检查可能会失败,因为它处于上述状态之一。
我建议您只需检查任务
的引用即可;如果为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;
});
}
}
关于c# - .NET 任务/TPL 测试和模拟? (或者用法不正确?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9486076/