2014-01-06 18 views
15

Jetzt weiß ich, Eigenschaften unterstützen nicht async/erwarten aus guten Gründen. Aber manchmal müssen Sie eine zusätzliche Hintergrundverarbeitung von einem Property Setter starten - ein gutes Beispiel ist die Datenbindung in einem MVVM-Szenario.Korrekte Methode zum Aufrufen von asynchronen Methoden innerhalb eines datengebundenen Eigenschaftensetzers?

In meinem Fall habe ich eine Eigenschaft, die an das SelectedItem einer ListView gebunden ist. Natürlich setze ich den neuen Wert sofort auf das Hintergrundfeld und die Hauptarbeit der Eigenschaft ist erledigt. Die Änderung des ausgewählten Elements in der Benutzeroberfläche muss jedoch auch einen REST-Serviceaufruf auslösen, um basierend auf dem jetzt ausgewählten Element neue Daten zu erhalten.

Also muss ich eine asynchrone Methode aufrufen. Ich kann es natürlich nicht abwarten, aber ich möchte auch nicht den Anruf abbrechen und vergessen, da ich während der asynchronen Verarbeitung Ausnahmen übersehen konnte.

Nun mein nehmen ist folgende:

private Feed selectedFeed; 
public Feed SelectedFeed 
{ 
    get 
    { 
     return this.selectedFeed; 
    } 
    set 
    { 
     if (this.selectedFeed != value) 
     { 
      this.selectedFeed = value; 
      RaisePropertyChanged(); 

      Task task = GetFeedArticles(value.Id); 

      task.ContinueWith(t => 
       { 
        if (t.Status != TaskStatus.RanToCompletion) 
        { 
         MessengerInstance.Send<string>("Error description", "DisplayErrorNotification"); 
        } 
       }); 
     } 
    } 
} 

Ok so neben der Tatsache, dass ich den Umgang mit der Setter auf eine synchrone Methode bewegen konnte, ist dies der richtige Weg, ein solches Szenario zu behandeln? Gibt es eine bessere, weniger überladene Lösung, die ich nicht sehe?

Wäre sehr daran interessiert, einige andere Ansichten zu diesem Problem zu sehen. Ich bin ein bisschen neugierig, dass ich zu diesem konkreten Thema keine weiteren Diskussionen finden konnte, da es mir sehr häufig in MVVM-Apps vorkommt, die Databinding stark nutzen.

+0

Spaß Sache von hier vorsichtig sein, wenn Änderungen Ihrer Immobilie Handhabung während eine Anfrage REST im Gange ist. Vor allem, weil Sie nicht garantieren können, dass sie in der Reihenfolge ausgeführt werden, in der sie aufgerufen wurden. –

+0

Ja, das stimmt :), aber dieses Problem würde auftreten, egal ob ich einen Property-Setter verwende, um den REST-Aufruf oder ein Event oder irgendetwas anderes zu initiieren. Mein Take ist, alle noch laufenden Anfragen abzubrechen, wenn sich das ausgewählte Element ändert, bevor ein neues gesendet wird. –

+0

Sie könnten stattdessen einen Befehl an das Ereignis "selection changed" anhängen, anstatt zu überprüfen, ob das ausgewählte Element von der Benutzeroberfläche geändert wurde. –

Antwort

10

Ich habe eine NotifyTaskCompletion type in my AsyncEx library, die im Wesentlichen eine INotifyPropertyChanged Wrapper für Task/Task<T> ist. AFAIK gibt es sehr wenig Informationen derzeit verfügbar auf async kombiniert mit MVVM, so lassen Sie mich wissen, wenn Sie andere Ansätze finden.

Wie auch immer, der Ansatz NotifyTaskCompletion funktioniert am besten, wenn Ihre Aufgaben ihre Ergebnisse zurückgeben. Das heißt, von Ihrem aktuellen Codebeispiel aus sieht es so aus, als ob GetFeedArticles datengebundene Eigenschaften als Nebeneffekt setzt, anstatt die Artikel zurückzugeben. Wenn Sie diese Rückkehr Task<T> stattdessen machen, können Sie mit dem Code am Ende wie folgt:

private Feed selectedFeed; 
public Feed SelectedFeed 
{ 
    get 
    { 
    return this.selectedFeed; 
    } 
    set 
    { 
    if (this.selectedFeed == value) 
     return; 
    this.selectedFeed = value; 
    RaisePropertyChanged(); 
    Articles = NotifyTaskCompletion.Create(GetFeedArticlesAsync(value.Id)); 
    } 
} 

private INotifyTaskCompletion<List<Article>> articles; 
public INotifyTaskCompletion<List<Article>> Articles 
{ 
    get { return this.articles; } 
    set 
    { 
    if (this.articles == value) 
     return; 
    this.articles = value; 
    RaisePropertyChanged(); 
    } 
} 

private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    ... 
} 

dann Ihre Datenbindung Articles.Result auf die resultierende Sammlung zu bekommen verwenden können (was null bis GetFeedArticlesAsync abgeschlossen ist ist). Sie können NotifyTaskCompletion "out of the box" verwenden, um Daten ebenfalls an Fehler zu binden (z. B. Articles.ErrorMessage), und es verfügt über einige boolesche Convenience-Eigenschaften (IsSuccessfullyCompleted, IsFaulted), um Sichtbarkeitsschalter zu handhaben.

Beachten Sie, dass damit Vorgänge, die nicht in der richtigen Reihenfolge ausgeführt werden, ordnungsgemäß verarbeitet werden. Da Articles tatsächlich die asynchrone Operation selbst darstellt (anstelle der Ergebnisse direkt), wird sie sofort aktualisiert, wenn eine neue Operation gestartet wird. Sie werden also nie veraltete Ergebnisse sehen.

Sie haben keine haben, Datenbindung für Ihre Fehlerbehandlung zu verwenden.Sie können die gewünschte Semantik durch Ändern der GetFeedArticlesAsync; beispielsweise Ausnahmen behandeln sie zu Ihrem MessengerInstance indem:

private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    try 
    { 
    ... 
    } 
    catch (Exception ex) 
    { 
    MessengerInstance.Send<string>("Error description", "DisplayErrorNotification"); 
    return null; 
    } 
} 

Ebenso gibt keine Ahnung von automatischer Löschung ist eingebaut, aber auch hier ist es einfach zu GetFeedArticlesAsync hinzuzufügen:

private CancellationTokenSource getFeedArticlesCts; 
private async Task<List<Article>> GetFeedArticlesAsync(int id) 
{ 
    if (getFeedArticlesCts != null) 
    getFeedArticlesCts.Cancel(); 
    using (getFeedArticlesCts = new CancellationTokenSource()) 
    { 
    ... 
    } 
} 

Dies ist ein Bereich der aktuellen Entwicklung, also bitte Verbesserungen oder API-Vorschläge machen!

+0

Danke Stephen, das ist ein interessanter Ansatz. Dies scheint auch ein guter Weg zu sein, asynchrone Daten auszulösen, die aus dem ViewModel-Konstruktor geladen werden. Momentan verwende ich einen EventToCommand, um eine Methode auf der VM aufzurufen, um die VM mit Daten zu füllen (was ich natürlich auch nicht sehr mag). Ich denke, der Code ist sauberer als mein Ansatz. Werde es definitiv versuchen. –

+0

Mit Blick auf den Quellcode für AsyncEx, erwartete ich, dass irgendwo der Compiler Warnung CS4014 ignoriert wurde. Nachdem etwas herumgespielt wurde, scheint es tatsächlich so zu sein, dass ein "async" Methodenaufruf nicht "wartet", indem er seinen Rückgabewert einer Variablen zuweist (oder in diesem Fall an eine Funktion weitergibt), dass diese Warnung verschwindet. Weißt du, ob das beabsichtigt ist? –

+1

Ok, ja es ist beabsichtigt: https://msdn.microsoft.com/en-us/library/hh873131.aspx –

0
public class AsyncRunner 
{ 
    public static void Run(Task task, Action<Task> onError = null) 
    { 
     if (onError == null) 
     { 
      task.ContinueWith((task1, o) => { }, TaskContinuationOptions.OnlyOnFaulted); 
     } 
     else 
     { 
      task.ContinueWith(onError, TaskContinuationOptions.OnlyOnFaulted); 
     } 
    } 
} 

Verwendung im Eigentum

private NavigationMenuItem _selectedMenuItem; 
public NavigationMenuItem SelectedMenuItem 
{ 
    get { return _selectedMenuItem; } 
    set 
    { 
     _selectedMenuItem = val; 
     AsyncRunner.Run(NavigateToMenuAsync(_selectedMenuItem));   
    } 
} 

private async Task NavigateToMenuAsync(NavigationMenuItem newNavigationMenu) 
{ 
    //call async tasks... 
}