2013-02-21 27 views
20

IValueConverter内でトリガしたい非同期メソッドの場合。IValueConverterの非同期実装

良いですか?結果プロパティを呼び出すと、同期が強制的に強制されますか?

public async Task<object> Convert(object value, Type targetType, object parameter, string language) 
{ 
    StorageFile file = value as StorageFile; 

    if (file != null) 
    { 
     var image = ImageEx.ImageFromFile(file).Result; 
     return image; 
    } 
    else 
    { 
     throw new InvalidOperationException("invalid parameter"); 
    } 
} 

答えて

34

おそらくTask.Resultに電話をかけたくないという理由がいくつかあります。

まず、asyncコードがConfigureAwaitを使用して書かれていない限り、ブログでyou can deadlockを詳しく説明します。第二に、おそらく(同期的に)あなたのUIをブロックしたくないのです。ディスクから読み込み中に一時的に「読み込み中」または空白のイメージを表示し、読み込みが完了したら更新する方がよいでしょう。

個人的には、私は値変換器ではなく、私のViewModelのこの部分を作成します。私はブログ記事の一部にdatabinding-friendly ways to do asynchronous initializationを記述しています。それが私の最初の選択だろう。 値コンバーターが非同期バックグラウンド処理を開始するのはちょっと感じられません。

しかし、デザインを検討して、非同期型の値コンバータが本当に必要なものだと考えるならば、少し独創的にする必要があります。値コンバーターの問題は、を同期させることです。データバインディングはデータコンテキストで開始し、パスを評価してから値変換を呼び出します。データコンテキストおよびパスのみが変更通知をサポートします。

データコンテキストに(同期)値コンバータを使用して、元の値をデータバインディングに適したTaskオブジェクトに変換してから、プロパティバインディングでTaskのようなオブジェクトのプロパティを使用するだけです結果を得るためのオブジェクト。

は、ここで私が何を意味するかの例です:

<TextBox Text="" Name="Input"/> 
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}" 
      Text="{Binding Path=Result}"/> 

TextBoxだけの入力ボックスです。 TextBlockは、最初にをTextBoxの入力テキストに設定し、 "非同期"コンバータを介してそれを実行します。 TextBlock.TextはそのコンバーターのResultに設定されています。

コンバータは非常に簡単です:

public class MyAsyncValueConverter : MarkupExtension, IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     var val = (string)value; 
     var task = Task.Run(async() => 
     { 
      await Task.Delay(5000); 
      return val + " done!"; 
     }); 
     return new TaskCompletionNotifier<string>(task); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     return null; 
    } 

    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
     return this; 
    } 
} 

コンバータは最初の5秒を待つための非同期操作を開始し、その後、追加し、「やりました!」入力文字列の末尾に移動します。 TaskにはIPropertyNotifyChangedが実装されていないので、変換器の結果は単なるTaskなのではないので、次のリリースであるAsyncEx libraryを使用しています。それは、(この例では単純化し; full source is available)このようなものになります。一緒にこれらの作品を置くことによって

// Watches a task and raises property-changed notifications when the task completes. 
public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged 
{ 
    public TaskCompletionNotifier(Task<TResult> task) 
    { 
     Task = task; 
     if (!task.IsCompleted) 
     { 
      var scheduler = (SynchronizationContext.Current == null) ? TaskScheduler.Current : TaskScheduler.FromCurrentSynchronizationContext(); 
      task.ContinueWith(t => 
      { 
       var propertyChanged = PropertyChanged; 
       if (propertyChanged != null) 
       { 
        propertyChanged(this, new PropertyChangedEventArgs("IsCompleted")); 
        if (t.IsCanceled) 
        { 
         propertyChanged(this, new PropertyChangedEventArgs("IsCanceled")); 
        } 
        else if (t.IsFaulted) 
        { 
         propertyChanged(this, new PropertyChangedEventArgs("IsFaulted")); 
         propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage")); 
        } 
        else 
        { 
         propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted")); 
         propertyChanged(this, new PropertyChangedEventArgs("Result")); 
        } 
       } 
      }, 
      CancellationToken.None, 
      TaskContinuationOptions.ExecuteSynchronously, 
      scheduler); 
     } 
    } 

    // Gets the task being watched. This property never changes and is never <c>null</c>. 
    public Task<TResult> Task { get; private set; } 

    Task ITaskCompletionNotifier.Task 
    { 
     get { return Task; } 
    } 

    // Gets the result of the task. Returns the default value of TResult if the task has not completed successfully. 
    public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } } 

    // Gets whether the task has completed. 
    public bool IsCompleted { get { return Task.IsCompleted; } } 

    // Gets whether the task has completed successfully. 
    public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } } 

    // Gets whether the task has been canceled. 
    public bool IsCanceled { get { return Task.IsCanceled; } } 

    // Gets whether the task has faulted. 
    public bool IsFaulted { get { return Task.IsFaulted; } } 

    // Gets the error message for the original faulting exception for the task. Returns <c>null</c> if the task is not faulted. 
    public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

を、我々は、値変換の結果である非同期データコンテキストを作成しました。データバインディングに便利なラッパーは、Taskが完了するまでデフォルトの結果(通常はnullまたは0)を使用します。したがって、ラッパーのResultTask.Resultとは全く異なります。同期はブロックされず、デッドロックの危険もありません。

しかし、私は、値コンバータではなく、ViewModelに非同期ロジックを配置することを選択したいと思います。

+0

よろしくお願い致します。 viewmodelで非同期操作を行うことは、私が現在回避策として持っている解決策です。しかし、これはとてもいい感じです。私が彼らがコンバータにいたと思ういくつかの懸念があります。私はIAsyncValueConverterのようなものを見落としたかった。しかし、それはそうでないようです:-(。 私はそれが同じ問題を持ついくつかの他の人に役立つと思うのであなたの投稿をマークします:-) –

+0

非常にいいですが、私はあなたに質問したいなぜ変換器は 'MarkupExtension'を拡張し、なぜ' ProvideValue'がそれ自身を返すのでしょうか? – Alberto

+1

@Alberto:リソース・ディクショナリ内のグローバル・インスタンスを宣言し、マークアップから参照する必要がないので、XAMLの便利さです。 –

関連する問題