Task.Result
いくつかの理由から、おそらく電話をかけたくないでしょう。
まず、ブログで詳しく説明しているように、コードがどこでも使用されていない限り、デッドロックが発生する可能性があります。次に、UIを(同期的に)ブロックしたくない場合があります。ディスクからの読み取り中に一時的に「読み込み中...」または空白のイメージを表示し、読み取りが完了したら更新することをお勧めします。async
ConfigureAwait
したがって、個人的には、値コンバータではなく、ViewModelのこの部分を作成します。非同期初期化を行うためのデータバインディングに適した方法を説明するブログ投稿があります。それが私の最初の選択です。値コンバーターが非同期のバックグラウンド操作を開始するのは正しくないと感じています。
ただし、設計を検討し、非同期値コンバーターが必要なものであると本当に考えている場合は、少し独創性を持たせる必要があります。値コンバーターの問題は、同期する必要があることです。データバインディングは、データコンテキストで開始され、パスを評価してから、値変換を呼び出します。データコンテキストとパスのみが変更通知をサポートします。
したがって、データコンテキストで(同期)値コンバーターを使用して、元の値をデータバインディングに適したオブジェクトに変換する必要があります。Task
その後、プロパティバインディングは、Task
-likeオブジェクトのプロパティの1つを使用して結果を取得します。
これが私が意味することの例です:
<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>
これTextBox
は単なる入力ボックスです。1つ目は、「非同期」コンバーターを介して実行するの入力テキストTextBlock
に独自の設定DataContext
を行います。そのコンバータのに設定されます。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秒間待機し、次に「done!」を追加します。入力文字列の最後まで。が実装されていないTask
ため、コンバーターの結果を単純なものにすることはできません。そのため、 AsyncExライブラリの次のリリースに含まれるタイプを使用しています。これは次のようになります(この例では簡略化されています。完全なソースが利用可能です):Task
IPropertyNotifyChanged
// 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
)を使用しますTask
。したがって、ラッパーは次のResult
ものとはまったく異なりTask.Result
ます。同期的にブロックされず、デッドロックの危険はありません。
ただし、繰り返しになりますが、値コンバーターではなく、非同期ロジックをViewModelに配置することを選択します。