2014-01-06 9 views
15

今はプロパティが非同期/待機をサポートしていないことが分かりました。しかし、プロパティーセッターから追加のバックグラウンド処理を開始する必要がある場合もあります。MVVMシナリオでは、データバインディングが良い例です。データバインドされたプロパティ設定ツール内から非同期メソッドを呼び出す正しい方法はありますか?

私の場合、ListViewのSelectedItemにバインドされているプロパティがあります。もちろん、私は直ちに新しい値をバッキングフィールドに設定し、プロパティの主な作業が行われます。しかし、UIで選択された項目の変更は、現在選択されている項目に基づいていくつかの新しいデータを取得するためにRESTサービス呼び出しをトリガーする必要もあります。

私は非同期メソッドを呼び出す必要があります。私はそれを待つことはできませんが、非同期処理中に例外が発生する可能性があるため、呼び出しを忘れて忘れたくはありません。

が今私のテイクは以下の通りです:

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]を事実のほかに、私は同期方法にセッターから取り扱いを移動できるので、これは、このようなシナリオを処理するための正しい方法は何ですか?私には見えない、より良い、あまり邪魔されない解決策がありますか?

この問題を解決するのに非常に興味がありますか。私は、この具体的な話題について他の議論を見つけることができず、データバインディングを大量に使用するMVVMアプリケーションでは私にとっては非常に共通しているように思うので、ちょっと不思議です。

+0

REST要求の進行中に、ここでの注意する楽しい事はときにプロパティの変更を処理しています。特に、呼び出された順に完了することが保証されていないためです。 –

+0

はい、それは当てはまります:)しかし、この問題は、プロパティセットを使用してREST呼び出しやイベントなどを開始しても問題は発生しません。私の取り組みは、選択したアイテムが新しいアイテムを送信する前に変更されたときに、まだ実行中のリクエストをすべてキャンセルすることです。 –

+0

代わりに、選択された項目がUIによって変更されたかどうかを確認する代わりに、変更された項目を選択するコマンドをアタッチすることができます。 –

答えて

10

私はNotifyTaskCompletion type in my AsyncEx libraryです。これは、Task/Task<T>の本質的にINotifyPropertyChangedラッパーです。 AFAIKには、MVVMと組み合わせたasyncに現在入手できる情報はほとんどありませんので、他の方法があれば教えてください。

とにかく、NotifyTaskCompletionアプローチは、タスクが結果を返す場合に最適です。つまり、現在のコードサンプルでは、​​GetFeedArticlesのように、記事を返す代わりに副作用としてデータバインディングプロパティを設定しています。あなたの代わりにこの戻りTask<T>を作る場合、あなたはこのようなコードで終わることができます。

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) 
{ 
    ... 
} 

次に、あなたのデータバインディングは、(nullGetFeedArticlesAsyncまで完了である)を得られたコレクションを取得するArticles.Resultを使用することができます。 NotifyTaskCompletionを「そのまま」使用すると、エラーにもデータバインドすることができます(たとえば、Articles.ErrorMessage)。可視性トグルを処理するには、いくつかのブール値の便利なプロパティ(IsSuccessfullyCompleted,)があります。

これは、正しく完了していない操作を正しく処理することに注意してください。 Articlesは実際には(結果ではなく)実際に非同期操作自体を表しているため、新しい操作が開始されると直ちに更新されます。だから、古い結果を見ることは決してありません。

には、エラー処理にデータバインディングを使用するがありません。GetFeedArticlesAsyncを変更することで、必要なセマンティクスを作成できます。例えば、あなたのMessengerInstanceに渡すことにより、例外処理するために:

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

すると同様に、内蔵の自動キャンセルの概念がありませんが、やはりそれはGetFeedArticlesAsyncに追加するのは簡単です:

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

です現在の開発領域ですので、改善やAPIの提案をしてください!

+0

Stephenありがとう、面白いアプローチです。これは、ViewModelのコンストラクタから非同期データをロードするための良い方法にも見えます。現在、私はEventToCommandを使用してVM上のメソッドを呼び出して、VMをデータで埋めます(もちろん、あまり好きではありません)。 私の考え方よりもコードがきれいだと思います。間違いなくそれを試してみましょう。 –

+0

AsyncExのソースコードを見ると、コンパイラ警告CS4014のどこかが無視されていることが予想されます。ちょっと遊んだ後に実際に起こっているのは、戻り値を変数に代入して(またはこの場合は関数に渡して) 'async'メソッド呼び出しを'待つ 'ことができないということです。それが意図的かどうか分かりますか? –

+1

はい、それは意図的です: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); 
     } 
    } 
} 

プロパティ内で使用

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... 
} 
関連する問題