Async Implementation of Ivalueconverter

Async implementation of IValueConverter

You probably don't want to call Task.Result, for a couple of reasons.

Firstly, as I explain in detail on my blog, you can deadlock unless your async code is has been written using ConfigureAwait everywhere. Secondly, you probably don't want to (synchronously) block your UI; it would be better to temporarily show a "loading..." or blank image while reading from the disk, and update when the read completes.

So, personally, I would make this part of my ViewModel, not a value converter. I have a blog post describing some databinding-friendly ways to do asynchronous initialization. That would be my first choice. It just doesn't feel right to have a value converter kicking off asynchronous background operations.

However, if you've considered your design and really think an asynchronous value converter is what you need, then you have to get a bit inventive. The problem with value converters is that they have to be synchronous: the data binding starts at the data context, evaluates the path, and then invokes a value conversion. Only the data context and path support change notifications.

So, you have to use a (synchronous) value converter in your data context to convert your original value into a databinding-friendly Task-like object and then your property binding just uses one of the properties on the Task-like object to get the result.

Here's an example of what I mean:

<TextBox Text="" Name="Input"/>
<TextBlock DataContext="{Binding ElementName=Input, Path=Text, Converter={local:MyAsyncValueConverter}}"
Text="{Binding Path=Result}"/>

The TextBox is just an input box. The TextBlock first sets its own DataContext to the TextBox's input text running it through an "asynchronous" converter. TextBlock.Text is set to the Result of that converter.

The converter is pretty simple:

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;
}
}

The converter first starts an asynchronous operation to wait 5 seconds and then add " done!" to the end of the input string. The result of the converter can't be just a plain Task because Task doesn't implement IPropertyNotifyChanged, so I'm using a type that will be in the next release of my AsyncEx library. It looks something like this (simplified for this example; 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;
}

By putting these pieces together, we've created an asynchronous data context that is the result of a value converter. The databinding-friendly Task wrapper will just use the default result (usually null or 0) until the Task completes. So the wrapper's Result is quite different than Task.Result: it won't synchronously block and there's no danger of deadlock.

But to reiterate: I'd choose to put asynchronous logic into the ViewModel rather than a value converter.

How to create a converter that use async and await in Windows 10?

Use this binding

<Image DataContext="{Binding ImageUrl, Converter={StaticResource ImageConverter}}" Stretch="Uniform" 
Source="{Binding Result}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

Converter class

public class ImageConverter : IValueConverter
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
public object Convert(object value, Type targetType, object parameter, string culture)
{
if (value != null)
{
string imageUrl = value.ToString();

if (imageUrl.Contains("NoImageIcon"))
return value;

if (imageUrl.Contains(Constants.IMAGES_FOLDER_PATH))
{
var task = Task.Run(()=>( (GetImage((String)value))));
return new TaskCompletionNotifier<BitmapImage>(task);

}

if (imageUrl.Contains("mp4"))
{
return new TaskCompletionNotifier<BitmapImage>(Task.Run(() => ((GetImage("ms-appx:///Images/video.png")))));

}

if (MCSManager.Instance.isInternetConnectionAvailable)
{
return new TaskCompletionNotifier<BitmapImage>(Task.Run(() => ((GetImage(value.ToString(),true)))));
}
else
{
var task = Task.Run(() => ((GetImage("ms-appx:///Images/defaultImage.png"))));
return new TaskCompletionNotifier<BitmapImage>(task);
}

}
return new Uri("ms-appx:///Images/defaultImage.png", UriKind.RelativeOrAbsolute);

}
private async Task<BitmapImage> GetImage(string path,bool link=false)
{
BitmapImage image=null;
var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
try
{

await dispatcher.RunAsync(CoreDispatcherPriority.Normal,async () =>
{
if (link)
{
image = new BitmapImage();
image.UriSource = new Uri(path, UriKind.RelativeOrAbsolute);

}
else
{
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;

StorageFile imagefile = await localFolder.GetFileAsync(path);
using (IRandomAccessStream fileStream = await imagefile.OpenAsync(FileAccessMode.ReadWrite))
{

image.SetSource(fileStream);

}
}
});
return image;
}
catch(Exception e)
{
return null;
}

}
public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
return null;
}

private async Task<bool> FileExists(string fileName)
{
try
{
StorageFile file = await localFolder.GetFileAsync(fileName);
return true;
}
catch (FileNotFoundException ex)
{
return false;
}
}
}
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; }

// 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; } }

public event PropertyChangedEventHandler PropertyChanged;
}

I'm returning task from converter and setting it to DataContext of image. Task has Result property,that i'm binding it to Source property

Referred from this link

Creating an async ValueConverter in MvvmCross

I found Stephen Cleary's answer to this question helpful: Async Implementation of IValueConverter

In the end I used his excellent AsyncEx library in a Windows-specific Value Converter to do async conversion of a byte array. For Android and iOS I was able to do the async conversion in MVVMCross custom bindings, without needing a Value Converter.

A little more information can be found in a blog post that I wrote: http://www.sequence.co.uk/blog/infinite-scrolling-using-mvvmcross-and-xamarin/

x:Bind converter with async method

panda is right, you really shouldn't open the file in the converter. This way, every time Convert() is called, you'll open the file and read from it, which is taking too much time and resources unnecessarily. Not to mention it hangs your UI thread and causes a deadlock. You should open the file before or while you are populating your ListView, read the list of URLs from it and bind to that (observable) collection instead.

If you want to stick with your current solution (which I strongly advise you against), please read the accepted answer here, Mr. Cleary describes the problem perfectly and in depth.

That's how I would do it: You'll need to make PlayerHistory an ObservableCollection of items, so the ListView will get notified when new items are added or old ones are removed from it. Based on your code snippet I suppose you already do it this way. The second thing you'll need is that you have to make the items in PlayerHistory (let's call them HistoryViewModel for now) implement the INotfyPropertyChanged interface, they have to have a property (let's call it PictureUri), which will you bind to in your DataTemplate, and they have to raise the PropertyChanged event in the setter of this property.
These written in code:

class HistoryViewModel : INotifyPropertyChanged
{
private Uri _pictureUri;

public Uri PictureUri
{
get
{
return _pictureUri;
}
set
{
if (value == _pictureUri)
return;

_pictureUri = value;

RaisePropertyChanged();
}
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Then your DataTemplate will look like this:

 <DataTemplate x:DataType="data:HistoryViewModel ">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image Source="{Binding PictureUri}" /> ...

This way whenever you update the PictureUri property of the backing ViewModels, the ListView's items will automagically be updated too, thanks to data binding.

How to return a value from a method, when the underlying API to be invoked is asynchronous

As SearchAsync return type is void, I cannot use the await keyword here. So how would I be able to design the function here?

SearchAsync returns void because it is an EAP method. You can convert it to a TAP method which is awaitable. I prefer to make TAP wrappers like this into extension methods, as such:

public static Task<IEnumerable<Contact>> SearchTaskAsync(this Contacts contacts, string filter, FilterKind filterKind)
{
var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
EventHandler<ContactsSearchEventArgs> subscription;
subscription = (_, args) =>
{
contacts.SearchComplete -= subscription;
try
{
tcs.TrySetResult(args.Results);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
contacts.SearchComplete += subscription;
contacts.SearchAsync(filter, filterKind, null);
return tcs.Task;
}

Once you write a SearchTaskAsync TAP extension method, you can call it like this:

private async Task<string> GetDisplayNameAsync(string number)
{
Contacts contactsBook = new Contacts();
var matches = await contactsBook.SearchTaskAsync(number, FilterKind.PhoneNumber);
...
return name;
}

However, this doesn't really solve the problem of how to display asynchronous data in a UI. And the answer (if you think about it) is that you can't, of course.

Instead, you have to design your UI so that it understands when it doesn't have the results yet (e.g., showing a spinner), and then update it when the search completes.

Note that this is tricky to pull off using a value converter. And that makes sense; you don't really want a value converter kicking off background operations! (You can hack it to work, but the results aren't pretty).

So I recommend that you use a separate VM property that is the display name. You can use NotifyTaskCompletion from my AsyncEx library to make this easier. Here's one example of how this could work:

public string Number
{
get { return _number; }
set
{
_number = value;
NumberDisplayName = NotifyTaskCompletion.Create(GetDisplayNameAsync(value));
RaisePropertyChanged("Number");
RaisePropertyChanged("NumberDisplayName");
}
}

public INotifyTaskCompletion<string> NumberDisplayName
{
get; private set;
}

Then your binding could use NumberDisplayName.Result to display the result (or a null string if the search has not completed). If you want a spinner or whatnot, you can use other paths such as NumberDisplayName.IsCompleted; and if you want proper error handling, there are paths for that as well (e.g., NumberDisplayName.ErrorMessage).

How to use IValueConverter implementation from another file?

Your IValueConverter class can be anywhere you want, including in another assembly. Based on your use, you need to have a resource somewhere accessible from your MainWindow class. An easy way is to have it in your window resources.

<MainWindow.Resources>
<my:IconExtractor x:Key="iconExtractor"/>
</MainWindow.Resources>

You can also use the resources in App.xaml, since its resources are accessible from all XAML based classes in your project. Using App.xaml appeals to me since you can avoid duplicating effort if more than one window in your project will use IconExtractor.

ivalueconverter vs system.converter vs delegate

The best approach according to me:

  • Let the user decide whether to use IValueConverter or Converter by implementing a class that offers both possibilities.
  • The lambda is out of scope because a lambda expression is what is used on the user side to easily provide a delegate implementation. This means that a ConvertingCollection would expect a delegate as parameter, which is what a Converter already is.
  • Finally, the idea to use an implicit conversion is not kept because :

    • It forces the user to provide a conversion that could apply anywhere and not only in the sole context of the usage of ConvertingCollection, which may be problematic sometimes
    • In case an implicit conversion applies, is is easy to use it in a lambda expression
    • It makes the implementation of the ConvertingCollection more complex (check if an implicit conversion exists between the 2 types A and B), and I'm too lazy for this. Ok, that may be the first reason, but the other two are still valid. :)

I hope this helps.

Improved IValueConverter -- MarkupExtension or DependencyObject?

Deriving from each gives you different kind of power and flexibility:

  • Deriving from MarkupExtension enables you to use the value converter without making it a static resource, as described below:

    public class DoubleMe : MarkupExtension, IValueConverter
    {
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
    return this;
    }
    public object Convert(object value, /*rest of parameters*/ )
    {
    if ( value is int )
    return (int)(value) * 2; //double it
    else
    return value.ToString() + value.ToString();
    }
    //...
    }

    In XAML, you can directly use it without creating a StaticResource:

    <TextBlock Text="{Binding Name, Converter={local:DoubleMe}}"/>
    <TextBlock Text="{Binding Age, Converter={local:DoubleMe}}"/>

    Such code is very handy when debugging, as you can just write local:DebugMe and then can debug the DataContext of the control on which you use it.

  • Deriving from DependencyObject enables you to configure the value converter with some preferences in a more expressive way, as described below:

    public class TruncateMe : DependencyObject, IValueConverter
    {
    public static readonly DependencyProperty MaxLengthProperty =
    DependencyProperty.Register("MaxLength",
    typeof(int),
    typeof(TruncateMe),
    new PropertyMetadata(100));
    public int MaxLength
    {
    get { return (int) this.GetValue(MaxLengthProperty); }
    set { this.SetValue(MaxLengthProperty, value); }
    }

    public object Convert(object value, /*rest of parameters*/ )
    {
    string s = value.ToString();
    if ( s.Length > MaxLength)
    return s.Substring(0, MaxLength) + "...";
    else
    return s;
    }
    //...
    }

    In XAML, you can directly use it as:

    <TextBlock>
    <TextBlock.Text>
    <Binding Path="FullDescription">
    <Binding.Converter>
    <local:TruncateMe MaxLength="50"/>
    </Binding.Converter>
    </Binding>
    </TextBlock.Text>

    What does it do? It truncates the string FullDescription if it is more than 50 characters!

@crazyarabian commented that:

Your statement "Deriving from DependencyObject enables you to configure the value converter with some preferences in a more expressive way" isn't exclusive to DependencyObject as you can create the same MaxLength property on a MarkupExtension resulting in <TextBlock Text="Binding Age, Converter={local:DoubleMe, MaxLength=50}}"/>. I would argue that a MarkupExtension is more expressive and less verbose.

That is true. But then that is not bindable; that is, when you derive from MarkupExtension, then you cannot do :

MaxLength="{Binding TextLength}"

But if you derive your converter from DependencyObject, then you can do the above. In that sense, it is more expressive compared to MarkupExtension.

Note that the target property must be a DependencyProperty for Binding to work. MSDN says,

  • Each binding typically has these four components: a binding target
    object, a target property, a binding
    source, and a Path to the value in the
    binding source to use. For example, if
    you want to bind the content of a
    TextBox to the Name property of an
    Employee object, your target object is
    the TextBox, the target property is
    the Text property
    , the value to use is
    Name, and the source object is the
    Employee object.

  • The target property must be a dependency property.



Related Topics



Leave a reply



Submit