2013-02-21 2 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를 호출하지 않습니다.

먼저 블로그에서 코드가 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은 먼저 DataContextTextBox의 입력 텍스트에 설정하고 "비동기"변환기를 통해이를 실행합니다. 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 초 후에 추가하는 비동기 작업을 시작합니다 "완료!" 입력 문자열의 끝에. TaskIPropertyNotifyChanged을 구현하지 못하기 때문에 변환기의 결과는 단지 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)를 사용합니다. 따라서 래퍼의 ResultTask.Result과 완전히 다릅니다. 동기식으로 차단되지 않으며 교착 상태가 발생할 위험이 없습니다.

하지만 반복하십시오. 비동기 로직을 ​​값 변환기가 아닌 ViewModel에 넣을 수 있습니다.

+0

안녕하세요 yoru 답장을 보내 주셔서 감사합니다. viewmodel에서 비동기 작업을 만드는 것은 현재 해결 방법으로 가지고있는 솔루션입니다. 그러나 이것은 매우 기분이 좋다. 나는 그들이 컨버터에 있다고 느낀 몇 가지 우려 사항이있다. 나는 IAsyncValueConverter와 같은 것을 간과하기를 희망했다. 그러나 그것은 그런 것 같지 않습니다 :-(. 나는 그것이 다른 문제를 가진 다른 사람들을 도울 것이라고 생각하기 때문에 당신의 게시물을 답글로 표시 할 것입니다 :-) –

+0

아주 좋은,하지만 당신에게 질문하고 싶습니다. 왜 변환기가'MarkupExtension'을 확장해야하고 왜'ProvideValue'가 스스로를 반환해야 하는가? – Alberto

+1

@Alberto : 리소스 사전에서 전역 인스턴스를 선언하고 마크 업에서 참조 할 필요가 없도록 XAML의 편리한 기능입니다. –

관련 문제