5

初期化に時間がかかるオブジェクトがあります。したがって、アプリケーションの起動時に初期化を開始する機能があります。クラスのメソッドへの後続の呼び出しには、クラスの初期化が完了するのを待機する遅延メカニズムが必要です。

私にはいくつかの解決策がありますが、どちらにも完全に満足しているわけではありません。1つ目はwhileループでTask.Delayを使用し、2つ目は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がオブジェクトを作成し、通常のオブジェクト インスタンスを受け取る非同期ファクトリになると思います。

スティーブン・トゥーブから惜しみなく盗む:

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-29T12:20:06.167 に答える
0

メソッド呼び出しを、初期化が終了したときに処理するキューに入れます。まだ初期化していない場合にのみ、メソッドをキューに入れます。

于 2012-06-29T11:58:02.453 に答える
0

アプリケーションがさまざまな状態にあるイベント駆動型アーキテクチャに移行できます。

最初に、アプリケーションは開始状態に移行します。この状態HeavyObjectでは、バックグラウンド タスクを使用して作成されます。初期化が完了すると、イベントが発生します。(実際の .NET を使用する必要はありませんevent。コールバックなどを使用できます。また、Reactive Extensions などのフレームワークを使用すると、一連のイベントを作成できます。)

すべての初期化イベントが発生すると、アプリケーションの開始状態に移行します。UI アプリケーションの場合、これにより UI が変更され、以前に無効にされていた操作が有効になる可能性があります。

于 2012-06-29T12:10:52.920 に答える