您可能不想撥打Task.Result
,原因有兩個。
首先,正如我在我的博客上詳細解釋的,you can deadlock除非您的async
代碼已被寫入使用ConfigureAwait
無處不在。其次,你可能不想(同步)阻止你的用戶界面;在從磁盤讀取數據時暫時顯示「正在加載...」或空白圖像並在讀取完成時更新會更好。
所以,我個人認爲我的ViewModel的這一部分不是價值轉換器。我有一篇博客文章描述了一些databinding-friendly ways to do asynchronous initialization。那將是我的第一選擇。如果有一個價值轉換器啓動了異步後臺操作,那不太合適。然而,如果你已經考慮過你的設計,並且真的認爲你需要一個異步值轉換器,那麼你必須有點創新。值轉換器的問題是它們的具有是同步的:數據綁定從數據上下文開始,評估路徑,然後調用值轉換。只有數據上下文和路徑支持才能更改通知。
因此,您必須在您的數據上下文中使用(同步)值轉換器將您的原始值轉換爲類似數據綁定的類對象,然後您的屬性綁定僅使用類似於Task
的屬性之一對象來獲得結果。
這裏是我的意思的例子:
<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>
的TextBox
只是一個輸入框。 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秒鐘,然後添加「完成了!」到輸入字符串的末尾。轉換器的結果不能只是一個普通的Task
,因爲Task
不實現IPropertyNotifyChanged
,所以我使用的類型將在我的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
),直到Task
完成。所以包裝的Result
與Task.Result
完全不同:它不會同步阻塞,也沒有死鎖的危險。
但要重申:我會選擇將異步邏輯放入ViewModel而不是值轉換器。
嗨感謝您的回覆。在viewmodel中進行異步操作確實是我目前作爲解決方法的解決方案。但是這感覺非常好。有一些擔心,我覺得他們在轉換器是正確的。我希望我忽略了像IAsyncValueConverter這樣的東西。但似乎沒有這樣的事情:-( 將標記您的帖子,雖然作爲一個答案,因爲我認爲它會幫助一些其他人有同樣的問題:-) – 2013-02-21 16:57:48
非常好,但我想問你一個問題:爲什麼轉換器應該擴展'MarkupExtension'和爲什麼'ProvideValue'返回自己? – Alberto 2013-10-15 11:09:53
@Alberto:這只是一個XAML便利,因此您不必在資源字典中聲明全局實例並從標記中引用它。 – 2013-10-15 12:22:49