おそらく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
)を使用します。したがって、ラッパーのResult
はTask.Result
とは全く異なります。同期はブロックされず、デッドロックの危険もありません。
しかし、私は、値コンバータではなく、ViewModelに非同期ロジックを配置することを選択したいと思います。
よろしくお願い致します。 viewmodelで非同期操作を行うことは、私が現在回避策として持っている解決策です。しかし、これはとてもいい感じです。私が彼らがコンバータにいたと思ういくつかの懸念があります。私はIAsyncValueConverterのようなものを見落としたかった。しかし、それはそうでないようです:-(。 私はそれが同じ問題を持ついくつかの他の人に役立つと思うのであなたの投稿をマークします:-) –
非常にいいですが、私はあなたに質問したいなぜ変換器は 'MarkupExtension'を拡張し、なぜ' ProvideValue'がそれ自身を返すのでしょうか? – Alberto
@Alberto:リソース・ディクショナリ内のグローバル・インスタンスを宣言し、マークアップから参照する必要がないので、XAMLの便利さです。 –