393

コンストラクターにデータを入力しようとしているプロジェクトがあります。

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コンストラクター内から直接呼び出すことができないのはなぜだろうと思っていました。おそらく多くの(明白な)エッジケースとそれに対する理由があります、私は何も考えられません。説明も探し回っていますが、見つからないようです。

4

12 に答える 12

396

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

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;   
    }
}  
于 2012-09-20T20:39:23.323 に答える
257

コンストラクターは、構築された型を返すメソッドと非常によく似た動作をします。また、asyncメソッドは任意の型を返すことはできません。「ファイア アンド フォーゲット」voidまたはTask.

type のコンストラクターがT実際に を返した場合Task<T>、それは非常に紛らわしいと思います。

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

これはすべて推測です。しかし、非同期コンストラクターの可能性があると、それだけの価値があるよりも多くの問題が発生するように思えます。

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

于 2011-11-16T01:49:40.487 に答える
78

あなたの問題は、ファイルオブジェクトの作成とファイルを開くことに匹敵します。実際、オブジェクトを実際に使用する前に 2 つのステップを実行しなければならないクラスがたくさんあります: 作成 + 初期化 (しばしば Open に似たものと呼ばれます)。

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

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

これを簡単にするパターンは、コンストラクターをプライベートに宣言し、オブジェクトを構築して構築されたオブジェクトをInitialize()返す前に呼び出す public static 関数を作成することです。このようにして、オブジェクトにアクセスできるすべての人が関数を使用したことがわかります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 ... // return something useful
    }

使い方は以下になります。

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);
}
于 2015-07-17T08:56:00.997 に答える
4

この特定のケースでは、タスクを起動し、完了時にビューに通知するために 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 がサポートされています。:)

于 2014-12-31T18:53:36.840 に答える
3

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

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

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

于 2011-11-16T01:28:51.017 に答える
1

コンストラクターで async を呼び出すと、デッドロックが発生する可能性があります

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

于 2012-11-07T21:54:35.433 に答える
-1

you can use Action inside Constructor

 public class ViewModel
    {
        public ObservableCollection<TData> Data { get; set; }
       public ViewModel()
        {              
            new Action(async () =>
            {
                  Data = await GetDataTask();
            }).Invoke();
        }

        public Task<ObservableCollection<TData>> GetDataTask()
        {
            Task<ObservableCollection<TData>> task;
            //Create a task which represents getting the data
            return task;
        }
    }
于 2013-03-20T08:55:35.960 に答える
-4

私はこの簡単なトリックを使用します。

public sealed partial class NamePage
{
  private readonly Task _initializingTask;

  public NamePage()
  {
    _initializingTask = Init();
  }

  private async Task Init()
  {
    /*
    Initialization that you need with await/async stuff allowed
    */
  }
}
于 2013-09-06T15:19:51.923 に答える
-5

私は async キーワードに詳しくありません (これは Silverlight 固有のものですか、それとも Visual Studio のベータ版の新機能ですか?)、なぜこれができないのかについては理解できると思います。

私が行った場合:

var o = new MyObject();
MessageBox(o.SomeProperty.ToString());

o 次のコード行が実行される前に初期化が完了していない可能性があります。コンストラクターが完了するまでオブジェクトのインスタンス化を割り当てることはできず、コンストラクターを非同期にしてもそれは変わらないので、ポイントは何でしょうか? ただし、コンストラクターから非同期メソッドを呼び出すと、コンストラクターが完了し、非同期メソッドがまだオブジェクトをセットアップするために必要なことを実行している間にインスタンス化を取得できます。

于 2011-11-16T01:29:29.220 に答える