web-dev-qa-db-ja.com

コンストラクターを非同期にすることはできますか?

私はコンストラクタでいくつかのデータを投入しようとしているプロジェクトがあります:

public class ViewModel
{
    public ObservableCollection<TData> Data { get; set; }

    async public ViewModel()
    {
        Data = await GetDataTask();
    }

    public Task<ObservableCollection<TData>> GetDataTask()
    {
        Task<ObservableCollection<TData>> task;

        //Create a task which represents getting the data
        return task;
    }
}

残念ながら、エラーが発生しています。

修飾子asyncはこのアイテムでは無効です

もちろん、標準メソッドでラップし、コンストラクターから呼び出す場合:

public async void Foo()
{
    Data = await GetDataTask();
}

正常に動作します。同様に、古いインサイドアウトの方法を使用する場合

GetData().ContinueWith(t => Data = t.Result);

それも機能します。なぜコンストラクタ内から直接awaitを呼び出せないのか疑問に思っていました。たぶん、多くの(明白な)エッジのケースとそれに対する理由があります、私はただ考えられません。私も説明を探しましたが、見つけられないようです。

226
Marty Neal

コンストラクターは、構築された型を返すメソッドと非常によく似ています。また、asyncメソッドはどの型も返せず、「fire and forget」voidまたはTaskでなければなりません。

タイプTのコンストラクターが実際にTask<T>を返した場合、それは非常に紛らわしいでしょう。

非同期コンストラクターがasync voidメソッドと同じように動作する場合、その種のコンストラクターの意図が破られます。コンストラクターが戻った後、完全に初期化されたオブジェクトを取得する必要があります。将来の未定義のポイントで実際に適切に初期化されるオブジェクトではありません。つまり、幸運で非同期初期化が失敗しない場合です。

これはすべて推測に過ぎません。しかし、非同期コンストラクタの可能性があることは、それが価値があるよりも多くのトラブルをもたらすように思えます。

実際にasync voidメソッドの「ファイアアンドフォーゲット」セマンティクス(必要に応じて回避する必要があります)が必要な場合は、質問で述べたように、すべてのコードをasync voidメソッドに簡単にカプセル化し、コンストラクターから呼び出すことができます。

174
svick

非同期コンストラクターを作成することはできないため、プライベートコンストラクターによって作成されたクラスインスタンスを返す静的非同期メソッドを使用します。これはエレガントではありませんが、大丈夫です。

   public class ViewModel       
   {       
    public ObservableCollection<TData> Data { get; set; }       

    //static async method that behave like a constructor       
    async public static Task<ViewModel> BuildViewModelAsync()  
    {       
     ObservableCollection<TData> tmpData = await GetDataTask();  
     return new ViewModel(tmpData);
    }       

    // private constructor called by the async method
    private ViewModel(ObservableCollection<TData> Data)
    {
     this.Data=Data;   
    }
   }  
185

あなたの問題は、ファイルオブジェクトの作成とファイルを開くことに匹敵します。実際、オブジェクトを実際に使用する前に2つの手順を実行する必要があるクラスが多数あります。作成+初期化(多くの場合、Openに似たものと呼ばれます)。

これの利点は、コンストラクターを軽量化できることです。必要に応じて、オブジェクトを実際に初期化する前にいくつかのプロパティを変更できます。すべてのプロパティが設定されると、Initialize/Open関数が呼び出され、使用するオブジェクトを準備します。このInitialize関数は非同期にすることができます。

欠点は、クラスの他の関数を使用する前にInitialize()を呼び出すクラスのユーザーを信頼する必要があることです。実際、クラスを完全なプルーフ(フールプルーフ?)にしたい場合は、Initialize()が呼び出されたすべての関数をチェックインする必要があります。

これを簡単にするパターンは、コンストラクターをプライベートに宣言し、オブジェクトを構築し、構築されたオブジェクトを返す前にInitialize()を呼び出すパブリックな静的関数を作成することです。これにより、オブジェクトにアクセスできるすべての人がInitialize関数を使用したことがわかります。

この例は、目的の非同期コンストラクターを模倣するクラスを示しています

public MyClass
{
    public static async Task<MyClass> CreateAsync(...)
    {
        MyClass x = new MyClass();
        await x.InitializeAsync(...)
        return x;
    }

    // make sure no one but the Create function can call the constructor:
    private MyClass(){}

    private async Task InitializeAsync(...)
    {
        // do the async things you wanted to do in your async constructor
    }

    public async Task<int> OtherFunctionAsync(int a, int b)
    {
        return await OtherFunctionAsync(a, b);
    }

使用方法は次のとおりです。

public async Task<int> SomethingAsync()
{
    // Create and initialize a MyClass object
    MyClass myObject = await MyClass.CreateAsync(...);

    // use the created object:
    return await myObject.OtherFunctionAsync(4, 7);
}
39

この特定のケースでは、タスクを起動し、完了時にビューに通知するためにviewModelが必要です。 「非同期コンストラクター」ではなく「非同期プロパティー」が適切です。

AsyncMVVM をリリースしました。これは(とりわけ)この問題を正確に解決します。使用すると、ViewModelは次のようになります。

public class ViewModel : AsyncBindableBase
{
    public ObservableCollection<TData> Data
    {
        get { return Property.Get(GetDataAsync); }
    }

    private Task<ObservableCollection<TData>> GetDataAsync()
    {
        //Get the data asynchronously
    }
}

奇妙なことに、Silverlightがサポートされています。 :)

4

なぜコンストラクタ内から直接awaitを呼び出せないのか疑問に思っていました。

私は簡単な答えは単純だと思います:.Netチームはこの機能をプログラムしていないからです。

私は正しい構文でこれを実装することができ、混乱しすぎたり、エラーが発生したりするべきではないと考えています。 Stephen Clearyの ブログ投稿 と他のいくつかの回答は、それに対する根本的な理由はないことを暗黙のうちに指摘しており、それ以上は回避策でその不足を解決したと思います。これらの比較的単純な回避策の存在は、おそらくこの機能が(まだ)実装されていない理由の1つです。

2
tsemer

コンストラクタを非同期にすると、オブジェクトを作成した後、インスタンスオブジェクトではなくnull値などの問題が発生する可能性があります。例えば;

MyClass instance = new MyClass();
instance.Foo(); // null exception here

それが彼らがこれを許可しない理由だと思います。

2
Emir Akaydın

コンストラクタで非同期を呼び出すと、デッドロックが発生する可能性があります。 http://social.msdn.Microsoft.com/Forums/en/winappswithcsharp/thread/0d24419e-36ad-4157-abb5-3d9e6c5dacf1

http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx

1
Tealc Wu

いくつかの答えには、新しいpublicメソッドの作成が含まれます。これを行わずに、Lazy<T>クラスを使用します。

public class ViewModel
{
    private Lazy<ObservableCollection<TData>> Data;

    async public ViewModel()
    {
        Data = new Lazy<ObservableCollection<TData>>(GetDataTask);
    }

    public ObservableCollection<TData> GetDataTask()
    {
        Task<ObservableCollection<TData>> task;

        //Create a task which represents getting the data
        return task.GetAwaiter().GetResult();
    }
}

Dataを使用するには、Data.Valueを使用します。

0
johnsmith