我正在尝试测试一些依赖Task执行某些后台计算的类(从网络位置检索数据)。该类获取一个未启动的Task实例,添加一个ContinueWith方法,然后在Task上调用Start。像这样:
private void Search()
{
Task<SearchResults> searchTask = m_searchProvider.GetSearchTask(m_searchCriteria);
searchTask.ContinueWith(OnSearchTaskCompleted);
searchTask.Start();
}
该类通过接口获取Task的实例,因此我能够注入我的测试所控制的Task实例。然而,我似乎无法创造一个我有足够控制权的人。
我不想在测试中引入线程,但仍希望使Task以异步方式运行,所以我试图做的是编写一个实现BeginInvoke / EndInvoke模式而没有线程化的类,并使用TaskFactory .FromAsync方法来创建任务。
这个想法是测试可以在启动任务的类上调用方法,然后当返回测试时,可以将结果数据提供给Async对象,该对象在保持同一线程的同时完成操作。 / p>
但是,当我尝试在该任务上调用Start时,我收到一条错误消息,指出“可能无法在具有空操作的任务上调用Start”。不幸的是,谷歌对这条消息没什么帮助,所以我不确定我是否错误地实现了我的Async对象,或者错误地使用了TaskFactory.FromAsync。这是我的NonThreadedAsync类的代码和一个异常的测试:
public class NonThreadedAsync<TResult>
{
private NonThreadedAsyncResult<TResult> m_asyncResult;
public IAsyncResult BeginInvoke(
AsyncCallback callback,
object state)
{
m_asyncResult = new NonThreadedAsyncResult<TResult>(callback, state);
return m_asyncResult;
}
public TResult EndInvoke(IAsyncResult asyncResult)
{
return m_asyncResult.GetResults();
}
public void Complete(TResult data)
{
m_asyncResult.CompleteAsync(data);
}
}
public class NonThreadedAsyncResult<TResult> : IAsyncResult
{
private readonly AsyncCallback m_asyncCallback;
private readonly object m_state;
private readonly ManualResetEvent m_waitHandle;
private bool m_isCompleted;
private TResult m_resultData;
public NonThreadedAsyncResult(AsyncCallback asyncCallback, object state)
{
m_asyncCallback = asyncCallback;
m_state = state;
m_waitHandle = new ManualResetEvent(false);
m_isCompleted = false;
}
public void CompleteAsync(TResult data)
{
m_resultData = data;
m_isCompleted = true;
m_waitHandle.Set();
if (m_asyncCallback != null)
{
m_asyncCallback(this);
}
}
public TResult GetResults()
{
if (!m_isCompleted)
{
m_waitHandle.WaitOne();
}
return m_resultData;
}
#region Implementation of IAsyncResult
public bool IsCompleted
{
get { return m_isCompleted; }
}
public WaitHandle AsyncWaitHandle
{
get { return m_waitHandle; }
}
public object AsyncState
{
get { return m_state; }
}
public bool CompletedSynchronously
{
get { return false; }
}
#endregion
}
[TestClass]
public class NonThreadedAsyncTests
{
[TestMethod]
public void TaskFactoryFromAsync_CanStartReturnedTask()
{
NonThreadedAsync<int> async = new NonThreadedAsync<int>();
Task<int> task = Task<int>.Factory.FromAsync(async.BeginInvoke, async.EndInvoke, null);
task.Start();
}
}
作为进一步的信息,如果我在调用Start()之前调试该测试,则任务实例将显示在Locals窗口中,如下所示:
Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
但是如果我展开它,可见属性中没有Method属性。
谁能看到我做错了什么?
[编辑:我还编写了一个测试,确认NonThreadedAsync类可以正常使用经典的Begin / End模式(或至少,我了解Begin / End模式:))这传递了:
[TestMethod]
public void NonThreadedAsync_ClassicAccessPattern()
{
int result = 0;
bool asyncCompleted = false;
NonThreadedAsync<int> async = new NonThreadedAsync<int>();
async.BeginInvoke(asyncResult =>
{
result = async.EndInvoke(asyncResult);
asyncCompleted = true;
},
null);
Assert.IsFalse(asyncCompleted);
Assert.AreEqual(0, result);
async.Complete(54);
Assert.IsTrue(asyncCompleted);
Assert.AreEqual(54, result);
}
答案 0 :(得分:5)
哦,我明白了。我们的API错误,因为它试图返回未启动的任务。从被测试的类中删除Start()可以解决问题。然而,在研究这个问题时,我发现我做了太多的事情来获得测试控制的异步任务。根据Stephen Toub的帖子here,我们可以简单地使用TaskCompletionSource:
[TestMethod]
public void TaskCompletionSource_WorksALotBetterThanMyOverEngineeredCustomStuff()
{
int result = 0;
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task<int> myTask = tcs.Task;
// Pretend this is the class under test and that we've
// passed in myTask
myTask.ContinueWith(t => { result = t.Result; },
TaskContinuationOptions.ExecuteSynchronously);
Assert.AreEqual(0, result);
tcs.SetResult(54);
Assert.AreEqual(54, result);
}