是否有在后台线程上初始化对象的常见模式?

时间:2012-06-29 11:42:53

标签: c# .net multithreading background parallel-processing

我有一个需要很长时间才能初始化的对象。因此,我有能力在应用程序启动时开始初始化。对类的方法的任何后续调用我们需要有一个等待类完成初始化的延迟机制。

我有几个潜在的解决方案,但我对其中任何一个都不满意。第一个在while循环中使用Task.Delay,第二个使用SemaphoreSlim但是涉及一些不必要的阻塞。我觉得这必须是一个相当普遍的要求,任何人都可以就如何最好地管理这个提出一些建议吗?

哦顺便说一下,这是一个Metro应用程序,所以我们有限制的API

这是伪代码:

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();
        }
    }
}

5 个答案:

答案 0 :(得分:1)

您可以使用Task<T>。这将为您处理所有同步,并允许您阻止直到可用的值:

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;  
}

或者,您也可以查询对象是否可用:

public static bool IsHeavyObjectAvailable
{
    get { return heavyObjectInitializer.IsCompleted; }
}

答案 1 :(得分:1)

我认为更好的设计是异步工厂,其中调用代码await是对象创建,然后接收常规对象实例。

自由地偷窃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");

这样,您可以很好地遵守单一责任原则:

  • AsyncLazy<T>Task<T>Lazy<T>结合在一起(因此仅在需要时才会异步创建实例)。
  • ExposeSomeInterestingItemsFactory包含构造逻辑。
  • ExposeSomeInterestingItems仅关注暴露有趣的项目,而不是必须以异步延迟污染其所有成员。

此外,此解决方案始终是异步的(无阻塞),这很好(特别是对于Metro应用程序)。

更新,2012-09-14:我已经使用了此代码并对其进行了清理并对其进行了评论on my blog

答案 2 :(得分:0)

将方法调用放入您完成初始化时处理的队列中。在尚未初始化时,只将方法放入队列。

答案 3 :(得分:0)

您可以转移到一个事件驱动的体系结构,您的应用程序处于不同的状态。

最初,应用程序进入 Starting 状态。在此状态下,使用后台任务创建HeavyObject。初始化完成后,将触发事件。 (您不必使用实际的.NET event。您可以使用回调或类似的东西,而Reactive Extensions等框架允许您组合事件序列。)

当所有初始化事件都被触发后,您将进入应用程序的已启动状态。对于UI应用程序,这可以修改UI以启用一些先前禁用的操作。

答案 4 :(得分:0)

检查Prototype Pattern。也许它可以帮到你

您只需要创建一次对象,并在需要另一个时克隆它。