2013-04-09 17 views
11

私のサービスクラスでRAIIパターンに従おうとしています。つまり、オブジェクトの構築時に完全に初期化されます。しかし、私は非同期APIの問題に直面しています。問題のクラスの構造は、私もそれがスレッドセーフにするために、ImportantValueゲッターに副作用を取り除くためにターゲットにしています非同期待機パターンを使用してオブジェクトを初期化する方法

class ServiceProvider : IServiceProvider // Is only used through this interface 
{ 
    public int ImportantValue { get; set; } 
    public event EventHandler ImportantValueUpdated; 

    public ServiceProvider(IDependency1 dep1, IDependency2 dep2) 
    { 
     // IDependency1 provide an input value to calculate ImportantValue 
     // IDependency2 provide an async algorithm to calculate ImportantValue 
    } 
} 

を次のようになります。

ServiceProviderのユーザーは、そのインスタンスを作成し、ImportantValueというイベントにサブスクライブし、最初にImportantValueを取得します。そしてここで、初期値とともに問題が起こります。 ImportantValueは非同期に計算されるため、クラスはコンストラクターで完全に初期化できません。この値を最初はnullにしても構いませんが、最初に計算される場所が必要です。そのための自然な場所はImportantValueのゲッターかもしれませんが、私はそれをスレッドセーフで、副作用なしにすることを目標にしています。

私は基本的にこれらの矛盾に固執しています。私を助けて、代替案を提供してもらえますか? niceは実際には必要ではないが、副作用やプロパティのスレッドセーフなどは不要ですが、コンストラクタで値を初期化することは必須です。

ありがとうございます。

編集:もう1つ追加します。インスタンス化のためにNinjectを使用していますが、わかっている限り、バインディングを作成するための非同期メソッドはサポートされていません。コンストラクタでタスクベースの操作を開始するアプローチはうまくいくが、私はその結果を待つことはできない。

I.e. 2次のアプローチはコンパイルされません、タスクが返されているので、ない私のオブジェクト(の答えとして、これまで提供):

Kernel.Bind<IServiceProvider>().ToMethod(async ctx => await ServiceProvider.CreateAsync()) 

または

Kernel.Bind<IServiceProvider>().ToMethod(async ctx => 
{ 
    var sp = new ServiceProvider(); 
    await sp.InitializeAsync(); 
}) 

単純な結合動作しますが、私は待っていませんよStephen Clearyの提案するように、コンストラクターで非同期初期化を開始した結果:

Kernel.Bind<IServiceProvider>().To<ServiceProvider>(); 

...それは私にはうまく見えません。

+0

私はRAIIだとは思わない。おそらく最も重要な部分はリソースの割り当て解除です(ただし名前はそれを示唆していません)。 GCは非決定的な割り当て解除を引き起こすため、これはC#にはあまり関係ありません。 – svick

+0

私のlibary [AsyncContainer](https:// github。com/RafaelSalguero/AsyncContainer) – rafael

答えて

25

私はseveral approaches to async constructionを記述するブログ記事を持っています。

私はReedの説明どおりの非同期ファクトリメソッドを推奨しますが、不可能な場合もあります(たとえば依存性注入など)。あなたはその後、通常のタイプを構築しますが、建設だけ開始非同期初期化することを心に留めておくことができます

public sealed class MyType 
{ 
    public MyType() 
    { 
     Initialization = InitializeAsync(); 
    } 

    public Task Initialization { get; private set; } 

    private async Task InitializeAsync() 
    { 
     // Asynchronously initialize this instance. 
     await Task.Delay(100); 
    } 
} 

:これらの例では、次のような非同期の初期化パターンを使用することができます。あなたは初期化するタイプが必要な場合は、あなたのコードを実行できます。

await myTypeInstance.Initialization; 

Initializationがすでに完了している場合は、実行(同期)がawait過ぎて継続すること。


あなたは、実際にはasynchronous property, I have a blog post for that, too.それはAsyncLazy<T>から利益を得ることができるようにあなたの状況が聞こえるたい場合:

public sealed class MyClass 
{ 
    public MyClass() 
    { 
     MyProperty = new AsyncLazy<int>(async() => 
     { 
      await Task.Delay(100); 
      return 13; 
     }); 
    } 

    public AsyncLazy<int> MyProperty { get; private set; } 
} 
4

1つの可能性のあるオプションは、コンストラクタを使用する代わりに、これをファクトリメソッドに移動することです。

あなたのファクトリメソッドは、あなたが非同期的に初期化を実行できるようになるTask<ServiceProvider>を返しますが、ないImportantValueは(非同期)が計算されるまでに構築ServiceProviderを返すことができます。

これは、ユーザーが次のようなコードを書くことができます:

var sp = await ServiceProvider.CreateAsync(); 
int iv = sp.ImportantValue; // Will be initialized at this point 
+0

申し訳ありませんが、私のクラスはインターフェイスを実装する必要があり、インターフェイスを介してのみ使用することを忘れています。これは静的ファクトリメソッドを排除します。 – Haspemulator

+0

@Haspemulatorこの場合、あなたはどこかでファクトリメソッドを持っています(インターフェイスを構築することはできません)。 –

+0

CreateAsyncの外観は – Demodave

0

私はこれが古い質問ですけど、それは非常には、Googleに表示され、その最初のです率直に言って、受け入れられた答えは貧しい答えです。 await演算子を使用できるように、遅延を強制するべきではありません。

初期化メソッドへのより良いアプローチ:

private async Task<bool> InitializeAsync() 
{ 
    try{ 
     // Initialize this instance. 
    } 

    catch{ 
     // Handle issues 
     return await Task.FromResult(false); 
    } 

    return await Task.FromResult(true); 
} 

これは、あなたのオブジェクトを初期化するために、非同期フレームワークを使用しますが、それはブール値を返します。

これはより良いアプローチですか?まず、あなたは、IMHOが非同期フレームワークを使用する目的を完全に無効にするあなたのコードの遅延を強制していません。次に、非同期メソッドから何かを返すのは良い経験則です。この方法では、あなたの非同期メソッドが実際に動作したかどうかを知っています。タスクを返すことは、非同期メソッドでvoidを返すことと同じです。

+3

でしょうか? 'await Task。 Delay'は、「インスタンスを初期化するためにここで非同期で作業する」ためのプレースホルダです。実際のコードには「Task.Delay」はありません。 –

+2

私はあなたの答えを悪化させていると感じています。なぜなら、自分が何をしているかを明確にしていないからです。初期化/構築中に非同期動作を遅らせる必要があるということです。さまざまなレベルの経験者があなたの投稿を見ることができ、回答に役立つ情報はほとんどなく、明確ではないコーディングが不十分であることを実証しました。言い換えれば、非同期フレームワークの新しいユーザーがあなたの答えを見て、非同期初期化/構築を得るためにコードに遅延を実装するでしょう。 –

+0

@StephenClearyあなたは両方とも正しいです;-) –

1

あなたとまったく同じシナリオをサポートする私のAsyncContainer IoCコンテナを使用することができます。

また、このような非同期初期化子などの他の便利なシナリオでは、実行時の条件付きの工場をサポートしていますが、非同期および同期ファクトリ関数

//The email service factory is an async method 
public static async Task<EmailService> EmailServiceFactory() 
{ 
    await Task.Delay(1000); 
    return new EmailService(); 
} 

class Service 
{ 
    //Constructor dependencies will be solved asynchronously: 
    public Service(IEmailService email) 
    { 
    } 
} 

var container = new Container(); 
//Register an async factory: 
container.Register<IEmailService>(EmailServiceFactory); 

//Asynchronous GetInstance: 
var service = await container.GetInstanceAsync<Service>(); 

//Safe synchronous, will fail if the solving path is not fully synchronous: 
var service = container.GetInstance<Service>(); 
0

これは、非同期の初期化の@StephenClearyパターンにわずかな変更であるに依存します。

awaitInitializationTaskに発信者が「覚えている」必要はなく、initializationTaskについても知ることができます(実際はプライベートに変更されています)。

初期化されたデータを使用するすべてのメソッドでは、await _initializationTaskの最初の呼び出しがあります。 _initializationTaskオブジェクト自体がブール値セット(IsCompleted、 'await'メカニズムがチェックする)を持つので、これは即座に2回目を返します - それで、複数回初期化することについて心配しないでください。

私が知っている唯一のキャッチは、データを使用するあらゆる方法でそれを呼び出すことを忘れないでください。

public sealed class MyType 
{ 
    public MyType() 
    { 
     _initializationTask = InitializeAsync(); 
    } 

    private Task _initializationTask; 

    private async Task InitializeAsync() 
    { 
     // Asynchronously initialize this instance. 
     _customers = await LoadCustomersAsync(); 
    } 

    public async Task<Customer> LookupCustomer(string name) 
    { 
     // Waits to ensure the class has been initialized properly 
     // The task will only ever run once, triggered initially by the constructor 
     // If the task failed this will raise an exception 
     // Note: there are no() since this is not a method call 
     await _initializationTask; 

     return _customers[name]; 
    } 

    // one way of clearing the cache 
    public void ClearCache() 
    { 
     InitializeAsync(); 
    } 

    // another approach to clearing the cache, will wait until complete 
    // I don't really see a benefit to this method since any call using the 
    // data (like LookupCustomer) will await the initialization anyway 
    public async Task ClearCache2() 
    { 
     await InitializeAsync(); 
    } 
} 
関連する問題