당신은 아마 몇 가지 이유, Task.Result
를 호출하지 않습니다.
먼저 블로그에서 코드가 ConfigureAwait
을 사용하여 작성되지 않은 한 내 블로그에서 자세히 설명합니다 (you can deadlock). 둘째, UI를 (동 기적으로) 차단하고 싶지 않을 것입니다. 디스크에서 읽는 동안 일시적으로 "로드 중 ..."또는 빈 이미지를 표시하고 읽기가 완료되면 업데이트하는 것이 좋습니다.
개인적으로 저는이 부분을 값 변환기가 아닌 ViewModel로 만들 것입니다. 블로그 게시물에 databinding-friendly ways to do asynchronous initialization에 대한 설명이 있습니다. 그게 내 첫번째 선택이 될거야. 값 변환기이 비동기 백그라운드 작업을 시작하는 것은 옳지 않습니다.
그러나 설계를 고려하고 실제로 비동기식 값 변환기가 필요하다고 생각한다면 약간 독창적이어야합니다. 값 변환기의 문제점은 이이 동기라는 것입니다. 데이터 바인딩은 데이터 컨텍스트에서 시작하여 경로를 평가 한 다음 값 변환을 호출합니다. 데이터 컨텍스트와 경로 만 변경 알림을 지원합니다.
그래서, 당신은 데이터 바인딩 친화적 Task
-like 객체로 원래의 값을 변환하고 바인딩 재산 단지 Task
-like의 속성 중 하나를 사용하는 데이터 컨텍스트에서 (동기) 값 변환기를 사용해야 결과를 얻기위한 객체.
여기에 무슨 뜻인지의 예 :
<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
이 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
래퍼는 Task
이 완료 될 때까지 기본 결과 (보통 null
또는 0
)를 사용합니다. 따라서 래퍼의 Result
은 Task.Result
과 완전히 다릅니다. 동기식으로 차단되지 않으며 교착 상태가 발생할 위험이 없습니다.
하지만 반복하십시오. 비동기 로직을 값 변환기가 아닌 ViewModel에 넣을 수 있습니다.
안녕하세요 yoru 답장을 보내 주셔서 감사합니다. viewmodel에서 비동기 작업을 만드는 것은 현재 해결 방법으로 가지고있는 솔루션입니다. 그러나 이것은 매우 기분이 좋다. 나는 그들이 컨버터에 있다고 느낀 몇 가지 우려 사항이있다. 나는 IAsyncValueConverter와 같은 것을 간과하기를 희망했다. 그러나 그것은 그런 것 같지 않습니다 :-(. 나는 그것이 다른 문제를 가진 다른 사람들을 도울 것이라고 생각하기 때문에 당신의 게시물을 답글로 표시 할 것입니다 :-) –
아주 좋은,하지만 당신에게 질문하고 싶습니다. 왜 변환기가'MarkupExtension'을 확장해야하고 왜'ProvideValue'가 스스로를 반환해야 하는가? – Alberto
@Alberto : 리소스 사전에서 전역 인스턴스를 선언하고 마크 업에서 참조 할 필요가 없도록 XAML의 편리한 기능입니다. –