Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a common pattern for initializing object on a background thread?

I have an object that takes a long time to be initialized. Therefore I the capability to Start Initializing on application startup. Any subsequent calls to methods on the class we need to have a delay mechanism that waits for the class to finish initialization.

I have a couple of potential solutions however I am not entirely satisfied with either of them. The first uses Task.Delay in a while loop and the second uses SemaphoreSlim but involves some unnecessary blocking. I feel this must be a fairly common requirement, can anybody provide some advice on how to best manage this?

Oh btw, this is a Metro application so we have limited API's

Here is the pseudocode:

public class ExposeSomeInterestingItems
{
    private InitialisationState _initialised;
    private readonly SemaphoreSlim _waiter = 
        new SemaphoreSlim(0);

    public async Task StartInitialize()
    {
        if (_initialised == InitialisationState.Initialised)
        {
            throw new InvalidOperationException(
                "Attempted to initialise ActiveTrackDown" +
                "loads when it is already initialized");
        }

        _initialised = 
            InitialisationState.StartedInitialisation;

        new TaskFactory().StartNew(async () =>
        {
            // This takes some time to load
            this._interestingItems = 
                InterestingItemsLoader.LoadItems();
            _waiter.Release();
            _initialised = InitialisationState.Initialised;
        });
    }

    public InterestingItem GetItem(string id)
    {
        DelayUntilLoaded();
        DelayUntilLoadedAlternative();
    }

    private async Task DelayUntilLoaded()
    {
        if (_initialised == InitialisationState.NotInitialised)
        {
            throw new InvalidOperationException("Error " +
                "occurred attempting to access details on " +
                "ActiveTrackDownloads before calling initialise");
        }

        while (true)
        {
            if (_initialised == InitialisationState.Initialised)
            {
                return;
            }

            await Task.Delay(300);
        }
    }

    private async Task DelayUntilLoadedAlternative()
    {
        if (_initialised == InitialisationState.NotInitialised)
        {
            throw new InvalidOperationException(
                "Error occurred attempting to access details " + 
                "on ActiveTrackDownloads before calling initialise");
        }

        try
        {
            await _waiter.WaitAsync();
        }
        finally
        {
            _waiter.Release();
        }
    }
}
like image 854
Dan Ryan Avatar asked Nov 04 '22 21:11

Dan Ryan


2 Answers

I think that a better design would be an asynchronous factory, where the calling code awaits the object creation and then receives a regular object instance.

Stealing liberally from Stephen Toub:

public class AsyncLazy<T> : Lazy<Task<T>> 
{ 
  public AsyncLazy(Func<T> valueFactory) : 
      base(() => Task.Run(valueFactory)) { }

  public AsyncLazy(Func<Task<T>> taskFactory) : 
      base(() => Task.Run(taskFactory)) { } 

  public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); } 
}

public static class ExposeSomeInterestingItemsFactory
{
  public static AsyncLazy<ExposeSomeInterestingItems> Instance
  {
    get { return _instance; }
  }

  private static readonly AsyncLazy<ExposeSomeInterestingItems> _instance =
      new AsyncLazy<ExposeSomeInterestingItems>(() => new ExposeSomeInterestingItems());

  public static void StartInitialization()
  {
    var unused = Instance.Value;
  }
}

public class ExposeSomeInterestingItems
{
  public ExposeSomeInterestingItems()
  {
    // This takes some time to load
    this._interestingItems = InterestingItemsLoader.LoadItems();
  }

  public InterestingItem GetItem(string id)
  {
    // Regular logic. No "delays".
  }
}

...

var exposeSomeInterestingItems = await ExposeSomeInterestingItemsFactory.Instance;
var item = exposeSomeInterestingItems.GetItem("id");

That way, you keep the Single Responsibility Principle nicely:

  • AsyncLazy<T> combines Task<T> with Lazy<T> (so the instance is created asynchronously only when needed).
  • ExposeSomeInterestingItemsFactory contains construction logic.
  • ExposeSomeInterestingItems is only concerned with exposing interesting items, rather than having to pollute all its members with asynchronous delays.

Also, this solution is asynchronous throughout (no blocking), which is good (particularly for Metro apps).

Update, 2012-09-14: I've taken this code and cleaned it up and commented it on my blog.

like image 92
Stephen Cleary Avatar answered Nov 15 '22 02:11

Stephen Cleary


You can use the Task<T> for this. This will take care of all the synchronisation for you and allows you to block untill the value is available:

private static Task<HeavyObject> heavyObjectInitializer;

// Call this method during application initialization
public static void Bootstrap()
{
    heavyObjectInitializer = new Task<HeavyObject>(() =>
    {
        // creation of heavy object here
        return new HeavyObject();
    });

    // Start running the initialization right now on a 
    // background thread. We don't have to wait on this.
    heavyObjectInitializer.Start();
}

// Call this method whenever you need to use the object.
public static HeavyObject GetHeavyObject()
{
    // Get the initialized object, or block untill this 
    // instance gets available.
    return heavyObjectInitializer.Result;  
}

Optionally, you can also query to see if the object is available or not:

public static bool IsHeavyObjectAvailable
{
    get { return heavyObjectInitializer.IsCompleted; }
}
like image 23
Steven Avatar answered Nov 15 '22 01:11

Steven