I am trying to test some classes that rely on a task to do some background computing (getting data from a network location). The class receives an unused instance of the task, adds the ContinueWith method, and then calls Start on Task. Something like that:
private void Search() { Task<SearchResults> searchTask = m_searchProvider.GetSearchTask(m_searchCriteria); searchTask.ContinueWith(OnSearchTaskCompleted); searchTask.Start(); }
The class receives an instance of the task through the interface, so I can enter an instance of the task that controls my test. However, I cannot create one that I have enough.
I donโt want to inject threads into the test, but still want the task to be executed asynchronously, so I tried to write a class that implements the BeginInvoke / EndInvoke template without streaming and uses TaskFactory.FromAsync to create the task.
The idea is that the test can call a class method that starts the task, and then, when it returns the test, it can provide the result data to an Async object that completes the operation, remaining in the same thread.
However, when I try to call the โStartโ function in this task, I get the error message โStart cannot be caused by a task with a null actionโ. Unfortunately, Google doesn't help me much in this post, so I'm not sure if I implemented my Async object incorrectly or used TaskFactory.FromAsync incorrectly. Here is my code for my NonThreadedAsync classes and a test that explodes with the exception:
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(); } }
As additional information, if I debug this test, before calling Start (), the instance of the task appears in the Locals window as follows:
Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
but visibility properties are not visible in visible properties if I extend it.
Can anyone see what I'm doing wrong?
[Edit: I also wrote a test that confirms that the NonThreadedAsync class works correctly with the classic Begin / End pattern (or at least my understanding of the Begin / End pattern :)), and this passes:
[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); }