28

内でトリガーしたい非同期メソッドがありIValueConverterます。

Resultプロパティを呼び出して同期を強制するよりも良い方法はありますか?

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");
    }
}
4

2 に答える 2

52

Task.Resultいくつかの理由から、おそらく電話をかけたくないでしょう。

まず、ブログで詳しく説明しているように、コードがどこでも使用されていない限り、デッドロックが発生する可能性があります。次に、UIを(同期的に)ブロックしたくない場合があります。ディスクからの読み取り中に一時的に「読み込み中...」または空白のイメージを表示し、読み取りが完了したら更新することをお勧めします。asyncConfigureAwait

したがって、個人的には、値コンバータではなく、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を行います。そのコンバータのに設定されます。TextBoxTextBlock.TextResult

コンバーターは非常に単純です。

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ライブラリの次のリリースに含まれるタイプを使用しています。これは次のようになります(この例では簡略化されています。完全なソースが利用可能です):TaskIPropertyNotifyChanged

// 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に配置することを選択します。

于 2013-02-21T16:45:00.503 に答える
0

別のアプローチは、非同期ソースまたはデータをサポートする独自のコントロールを作成することです。

これが画像の例です

    public class AsyncSourceCachedImage : CachedImage
{
    public static BindableProperty AsyncSourceProperty = BindableProperty.Create(nameof(AsyncSource), typeof(Task<Xamarin.Forms.ImageSource>), typeof(AsyncSourceSvgCachedImage), null, propertyChanged: SourceAsyncPropertyChanged);

    public Task<Xamarin.Forms.ImageSource> AsyncSource
    {
        get { return (Task<Xamarin.Forms.ImageSource>)GetValue(AsyncSourceProperty); }
        set { SetValue(AsyncSourceProperty, value); }
    }

    private static async void SourceAsyncPropertyChanged(BindableObject bindable, object oldColor, object newColor)
    {
        var view = bindable as AsyncSourceCachedImage;
        var taskForImageSource = newColor as Task<Xamarin.Forms.ImageSource>;

        if (taskForImageSource != null)
        {
            var awaitedImageSource = await taskForImageSource;

            view.Source = awaitedImageSource;
        }
    }
}

さらに、タスクが解決されるまで、画像に読み込みアクティビティインジケーターを実装することもできます。

于 2020-06-02T13:53:08.023 に答える