476 Stimmen

Können Konstruktoren asynchron sein?

Ich habe ein Projekt, wo ich versuche, einige Daten in einem Konstruktor zu füllen:

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;
    }
}

Leider erhalte ich eine Fehlermeldung:

Der Modifikator async ist für diesen Artikel nicht gültig

Natürlich, wenn ich eine Standardmethode einpacke und diese vom Konstruktor aus aufrufe:

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

Es funktioniert gut. Ebenso, wenn ich die alte Methode von innen nach außen verwende

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

Das funktioniert auch. Ich habe mich nur gefragt, warum wir nicht anrufen können await direkt aus einem Konstruktor heraus. Es gibt wahrscheinlich viele (auch offensichtliche) Randfälle und Gründe dagegen, mir fallen nur keine ein. Ich habe auch nach einer Erklärung gesucht, aber ich kann keine finden.

514voto

Pierre Poliakoff Punkte 4359

Da es nicht möglich ist, einen asynchronen Konstruktor zu erstellen, verwende ich eine statische asynchrone Methode, die eine durch einen privaten Konstruktor erstellte Klasseninstanz zurückgibt. Das ist nicht elegant, aber es funktioniert gut.

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;   
    }
}

283voto

svick Punkte 224493

Constructor verhält sich ähnlich wie eine Methode, die den konstruierten Typ zurückgibt. Und async Methode kann nicht einfach irgendeinen Typ zurückgeben, sondern muss entweder "fire and forget" sein void ou Task .

Wenn der Konstruktor vom Typ T tatsächlich zurückgegeben Task<T> Das wäre sehr verwirrend, denke ich.

Wenn sich der async-Konstruktor genauso verhalten würde wie ein async void Methode zu verwenden, bricht das, was ein Konstruktor eigentlich sein sollte. Nachdem der Konstruktor zurückkehrt, sollten Sie ein vollständig initialisiertes Objekt erhalten. Nicht ein Objekt, das erst zu einem unbestimmten Zeitpunkt in der Zukunft richtig initialisiert wird. Das heißt, wenn Sie Glück haben und die asynchrone Initialisierung nicht fehlschlägt.

All dies ist nur eine Vermutung. Aber es scheint mir, dass die Möglichkeit eines asynchronen Konstruktors mehr Ärger bringt, als es wert ist.

Wenn Sie tatsächlich die "Feuer und Vergessen"-Semantik von async void Methoden (die nach Möglichkeit vermieden werden sollten), können Sie den gesamten Code einfach in einer async void Methode und rufen diese von Ihrem Konstruktor aus auf, wie Sie in der Frage erwähnt haben.

88voto

Harald Coppoolse Punkte 26687

Ihr Problem ist vergleichbar mit der Erstellung eines Dateiobjekts und dem Öffnen der Datei. In der Tat gibt es viele Klassen, bei denen Sie zwei Schritte durchführen müssen, bevor Sie das Objekt tatsächlich verwenden können: Erstellen + Initialisieren (oft als etwas Ähnliches wie Öffnen bezeichnet).

Dies hat den Vorteil, dass der Konstruktor leichtgewichtig sein kann. Falls gewünscht, können Sie einige Eigenschaften vor der eigentlichen Initialisierung des Objekts ändern. Wenn alle Eigenschaften gesetzt sind, wird die Initialize / Open Funktion wird aufgerufen, um das zu verwendende Objekt vorzubereiten. Diese Initialize Funktion kann asynchron sein.

Der Nachteil ist, dass Sie dem Benutzer Ihrer Klasse vertrauen müssen, dass er die Initialize() bevor er eine andere Funktion Ihrer Klasse verwendet. Wenn Sie Ihre Klasse völlig sicher machen wollen, müssen Sie in jeder Funktion prüfen, ob die Initialize() aufgerufen wurde.

Das Muster, um dies zu vereinfachen, besteht darin, den Konstruktor als privat zu deklarieren und eine öffentliche statische Funktion zu erstellen, die das Objekt konstruiert und die Initialize() bevor das konstruierte Objekt zurückgegeben wird. Auf diese Weise wissen Sie, dass jeder, der Zugriff auf das Objekt hat, die Initialize Funktion.

Das Beispiel zeigt eine Klasse, die den von Ihnen gewünschten asynchronen Konstruktor nachahmt

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
    }

Die Verwendung wird wie folgt aussehen:

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);
}

4voto

Dmitry Shechtman Punkte 6188

In diesem speziellen Fall ist ein viewModel erforderlich, um die Aufgabe zu starten und die Ansicht nach ihrem Abschluss zu benachrichtigen. Eine "async-Eigenschaft", nicht ein "async-Konstruktor", ist hier angebracht.

Ich habe gerade AsyncMVVM , das genau dieses Problem (neben anderen) löst. Sollten Sie es verwenden, würde Ihr ViewModel werden:

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

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

Seltsamerweise wird Silverlight unterstützt :)

4voto

Emir Akaydın Punkte 5542

Wenn Sie den Konstruktor asynchron machen, kann es nach der Erstellung eines Objekts zu Problemen wie Nullwerten anstelle von Instanzobjekten kommen. Zum Beispiel;

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

Deshalb ist das wohl auch nicht erlaubt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X