开发者

ManualResetEventSlim and Lock

开发者 https://www.devze.com 2023-02-03 14:03 出处:网络
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

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(...){}?


You might want to look into the Future<T> pattern. One implementation is available in the Magnum library: Future.cs. Basically, you return a Future<T> from a single GetData() method. You can decide whether to return the blocking or non-blocking version of your Future<T>. When the caller is ready to use the value, they can either check if the Future's value is ready or just ask for the Value and the Future will block until it gets the value.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消