I have a piece of data that takes quite a lot of time to fetch. I have different ways of figuring out if new data should be fetched or if I can use my current "cache" theResult
When someone asks for that piece of data I want to be able to both do a blocking and non blocking return.
Im not sure what the best way is to do that, I was considering something with ManualResetEventSlim and a lock:
NonBlocking:
theState = State.None;
public Data GetDataNonBlocking(){
lock(_myLock){
if (theState == State.Getting)
return null;
if (theState == State.Complete
return theData;
theState = State.Getting;
_resetEvent.Reset();
Task.Factory.StartNew(
()=>{
//<...Getting data.....>
theData= ...data....;
lock(_myLock){
theState = State.Complete;
_resetevent.Set();
}
});
return null;
}
}
Blocking:
public Data GetDataBlocking(){
lock(_myLock){
if (theState == State.Getting){
_resetevent.Wait();
return theData;
}
if (theState == State.Complete)
return theData;
_resetevent.Reset();
theState = State.Getting;
}
//.....
theData= 1234;
lock(_myLock){
State = State.Complete;
_resetevent.Set();
}
return theData;
}
But I'm not certain that is the way to do a thing like that. For example the _resetEvent.Wait()
inside a lock(...){}
?
I think that your encapsulation could use a little tweaking. For example, I think that you should separate the code that gets the data asynchronously to just this:
static class DataFactory
{
internal static DataType GetData()
{
// Return the data.
return new DataType();
}
}
Then, your class instance can worry separately about the state, and use the Task<T>
to facilitate that:
class DataManager
{
// The lock on the operation.
private readonly object lockObj = new object();
// The state.
private State theState = State.None;
// The task to get the state.
private Task<DataType> getDataTask;
public DataType GetDataAsync()
{
lock(lockObj)
{
if (theState == State.Getting)
return null;
if (theState == State.Complete
return getDataTask.Result;
// Set the state to getting.
theState = State.Getting;
// Start the task.
getDataTask = Task.Factory.StartNew(() => {
// Get the data.
DataType result = DataFactory.GetData();
// Lock and set the state.
lock (lockObj)
{
// Set the state.
theState = State.Complete;
}
// Return the result.
return result;
});
// Return null to indicate the operation started
// (is in "getting" state).
return null;
}
}
}
Now, because you are using Task<T>
, your GetDataBlocking
(I think it should be called GetData
) method becomes very simple:
public DataType GetData()
{
// Get the data async, if the result is non null, then
// return it.
DataType result = GetDataAsync();
// If it is non-null, return it.
if (result != null) return result;
// If at this point, the operation has been kicked off
// to load the data. Just wait on the task and return the result then.
getDataTask.Wait();
// Return the async data again, it will just return the
// result from the task.
return GetDataAsync();
}
In the end, I think you should keep more in line with the traditional async patterns exposed in .NET (either the Begin/End pattern, or event-based), as they will allow you to plug into other pipelines more easily.