5

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

我有几个潜在的解决方案,但我对其中任何一个都不完全满意。第一个在 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();
        }
    }
}
4

5 回答 5

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; }
}
于 2012-06-29T11:54:38.333 回答
1

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

从 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 年 9 月14 日更新:我已将这段代码清理干净,并在我的博客上发表了评论。

于 2012-06-29T14:02:31.910 回答
0

将方法调用放入完成初始化后处理的队列中。仅在尚未初始化时才将方法放入队列。

于 2012-06-29T11:58:02.453 回答
0

您可以迁移到应用程序处于不同状态的事件驱动架构。

最初,应用程序进入启动状态。在这种状态下HeavyObject是使用后台任务创建的。初始化完成后会触发一个事件。(您不必使用实际的 .NET event。您可以使用回调或类似的东西,像 Reactive Extensions 这样的框架允许您组合事件序列。)

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

于 2012-06-29T12:10:52.920 回答
0

检查这个原型模式。也许它可以帮助你

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

于 2012-06-29T12:20:06.167 回答