gpt4 book ai didi

c# - IValueConverter 的异步实现

转载 作者:IT王子 更新时间:2023-10-29 04:29:57 25 4
gpt4 key购买 nike

我有一个异步方法,我想在 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");
}
}

最佳答案

出于几个原因,您可能不想调用 Task.Result

首先,正如我在我的博客中详细解释的那样,you can deadlock除非您的 async 代码已在各处使用 ConfigureAwait 编写。其次,您可能不想(同步地)阻止您的 UI;最好在从磁盘读取时暂时显示“正在加载...”或空白图像,并在读取完成时更新。

因此,就个人而言,我会将这部分作为我的 ViewModel,而不是值转换器。我有一篇博文描述了一些 databinding-friendly ways to do asynchronous initialization .那将是我的第一选择。让值转换器启动异步后台操作感觉不对。

但是,如果您仔细考虑过自己的设计并确实认为异步值转换器是您所需要的,那么您就必须发挥一点创造力。值转换器的问题在于它们必须是同步的:数据绑定(bind)从数据上下文开始,评估路径,然后调用值转换。只有数据上下文和路径支持更改通知。

因此,您必须在数据上下文中使用(同步)值转换器将原始值转换为数据绑定(bind)友好的 Task 类对象,然后您的属性绑定(bind)仅使用其中一个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;
}

通过将这些部分放在一起,我们创建了一个异步数据上下文,它是值转换器的结果。数据绑定(bind)友好的 Task 包装器将只使用默认结果(通常是 null0)直到 Task 完成.所以包装器的 ResultTask.Result 完全不同:它不会同步阻塞并且没有死锁的危险。

但重申一下:我会选择将异步逻辑放入 ViewModel 而不是值转换器。

关于c# - IValueConverter 的异步实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15003827/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com