Property changed is not updating the UI from inside a task - c#

Firstly I have a user control which has a dependency property as follows. The MenuItems property is bound to some List control on the UI.
public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register(
nameof(MenuItems),
typeof(IEnumerable<MenuItem>),
typeof(MenuViewControl),
new PropertyMetadata(null));
public IEnumerable<MenuItem> MenuItems
{
get => (IEnumerable<MenuItem>)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
The MenuItem class is as follows which has 3 properties,
public class MenuItem : BindableBase
{
private string _text;
private Action _action;
private ICommand _executeCommand;
public string Text
{
get => _text;
set => Set(ref _text, value);
}
public Action Action
{
get => _action;
set => Set(ref _action, value);
}
public ICommand ExecuteCommand
{
get => _executeCommand ?? (_executeCommand = new RelayCommand(Action, _canExecute));
set
{
if (Set(ref _executeCommand, value))
{
CanExecute = () => _executeCommand?.CanExecute(null) ?? true;
_executeCommand.CanExecuteChanged += (sender, args) => RaisePropertyChanged(nameof(IsEnabled));
}
}
}
}
Now somewhere in my code I want to reuse the above user control. Along the same lines I need to call some async methods. So I have a view model class for the current UI where I will be calling the above user control as follows. My problem is the IsBorderProgressRingVisible is never being set to false and the RunMethodResult never updates the TextBlock in the current UI. Please help.
public class UserMaintenanceMethodsViewModel:BindableBase
{
//This collection is bound to the above UserControl's MenuItem property on my current UI.
private ObservableCollection<MenuItem> _userMaintenanceMenuCollection;
public ObservableCollection<MenuItem> UserMaintenanceMenuCollection
{
get => _userMaintenanceMenuCollection;
set => Set(ref _userMaintenanceMenuCollection, value);
}
//This string is bound to a textblock
private string _runMethodResult;
public string RunMethodResult
{
get => _runMethodResult;
set => Set(ref _runMethodResult, value);
}
//This property is bound to a progress ring.
private bool _isBorderProgressRingVisible;
public bool IsBorderProgressRingVisible
{
get => _isBorderProgressRingVisible;
set => Set(ref _isBorderProgressRingVisible, value);
}
//In my constructor I am calling some async methods as follows..
public UserMaintenanceMethodsViewModel()
{
_ = PopulateServiceMethods();
}
//Problem in this method is once the IsBorderProgressRingVisible is set to true, it never sets the value back to false. As a result the progress ring never collapses.
//The other problem is the RunMethodResult which is bound to a textblock never gets updated. Please help.
private async Task PopulateServiceMethods()
{
try
{
if (_atlasControlledModule != null)
{
IsBorderProgressRingVisible = true;
UserMaintenanceMenuCollection = new ObservableCollection<MenuItem>();
var Methods = await _atlasControlledModule.GetServiceMethods(AtlasMethodType.Maintenance).ConfigureAwait(true);
foreach (var method in Methods)
{
UserMaintenanceMenuCollection.Add(new MenuItem()
{
Text = method.Name,
Action = async () =>
{
var result = await ExcuteAtlasMethod(method).ConfigureAwait(true);
RunMethodResult = result.Status.ToString(); //The textblock on the UI never gets updated.
},
Warning = false
});
}
}
}
finally
{
IsBorderProgressRingVisible = false; //This code dosen't work.
}
}
private async Task<AtlasMethodRequest> ExcuteAtlasMethod(AtlasMethod method)
{
try
{
IsBorderProgressRingVisible = true;
return await _atlasControlledModule.CallMethod(method);
}
finally
{
IsBorderProgressRingVisible = false;
}
}
}
Edit: Here is the Xaml for the current view
<viewCommon:PageViewBase
x:Class="Presentation.InstrumentUI.ViewsLoggedIn.UserMaintenanceMethodsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewCommon="using:Presentation.InstrumentUI.Common"
xmlns:viewsCommon="using:Presentation.InstrumentUI.ViewsCommon"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:valueConverters="using:Presentation.Common.ValueConverters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<viewsCommon:MenuViewControl x:Name="UserMaintenanceMethodsMenuView"
Grid.Row="0"
Title="{Binding UserMaintenanceMethodsTitle, Source={StaticResource StringResources}}"
LifetimeScope="{x:Bind LifetimeScope}"
MenuItems="{x:Bind ViewModel.UserMaintenanceMenuCollection,Mode=OneWay}"
HeaderVisibility="Visible">
</viewsCommon:MenuViewControl>
</Grid>
</viewCommon:PageViewBase>
This is the xaml.cs
public sealed partial class UserMaintenanceMethodsView : PageViewBase
{
public IUserMaintenanceMethodsViewModel ViewModel { get; set; }
public UserMaintenanceMethodsView()
{
this.InitializeComponent();
ViewModel = LifetimeScope.Resolve<IUserMaintenanceMethodsViewModel>();
}
}

From what I see, you code should generally work. The problem is that all your code is executed in the constructor of UserMaintenanceMethodsViewModel. You shouldn't call long running methods from a constructor and you shouldn't call async methods from your constructor. An asynchronous method generally indicates some long running or CPU heavy operation. It should be moved outside the constructor so that you can execute it asynchronously.
Also the way you invoke the asynchronous method from the constructor is wrong:
ctor()
{
// Executes synchronously.
// Leaves object in undefined state as the constructor will return immediately.
_ = PopulateServiceMethodsAsync();
}
The previous example will execute the method PopulateServiceMethods synchronously. Furthermore, the constructor will return before the method has completed, leaving the instance in an uninitialized state.
The caller of the constructor will continue and probably use the instance, assuming that it is ready to use. This might lead to unexpected behavior.
To solve this, you should move the resource intensive initialization to a separate method:
ctor()
{
// Some instance member initialization
}
// Call later e.g. on first access of property internally or externally
public async Task InitializeAsync()
{
// Some CPU heavy or long running initialization routine
}
You can also consider to instantiate this type deferred using Lazy<T> or AsyncLazy<T>.
This property in MenuItem class has a "dangerous" setter:
public ICommand ExecuteCommand
{
get => _executeCommand ?? (_executeCommand = new RelayCommand(Action, _canExecute));
set
{
if (Set(ref _executeCommand, value))
{
CanExecute = () => _executeCommand?.CanExecute(null) ?? true;
_executeCommand.CanExecuteChanged += (sender, args) => RaisePropertyChanged(nameof(IsEnabled));
}
}
}
Calling the set method will replace the previous command without unsubscribing from the old CanExecuteChanged event. This can lead to memory leaks in certain scenarios. Always unsubscribe from the old instance before subscribing to the new instance.
Also I'm not quite sure why you are listening to this event at all. Usually controls listen to this event in. E.g., a Button subscribes to this event and when it is raised, it would invoke ICommand.CanExecute again to deactivate itself, if this method returns false. From your view model you usually want to call RaiseCanExecuteChanged on your command to trigger re-evaluation for all controls (or implementations of ICommandSource).
Using async in lambdas can also lead to unexpected behavior:
Action = async () =>
{
var result = await ExcuteAtlasMethod(method).ConfigureAwait(true);
RunMethodResult = result.Status.ToString(); // The textblock on the UI never gets updated.
}
Executing the Action won't cause the thread to wait asynchronously, because the delegate is not awaited. Execution continues. You should consider to implement the RelayCommand that it accepts a Func<object, Task>. This way the invocation of the delegate can be awaited.
{x:Bind} has a different behavior than {Binding}. x:Bind is a compiletime binding. It doesn't bind to the DataContext and requires a static binding source. You should debug your code in order to check if LifeTimeScope resolves properly. Maybe it executes on a different thread. You can try to change ViewModel to a DependencyProperty.
I also realized that you are binding to a property that is declared as interface type:
public IUserMaintenanceMethodsViewModel ViewModel { get; set; }
This won't work. Please try to replace the interface with a concrete type. I think this will solve this. E.g.,
public UserMaintenanceMethodsViewModel ViewModel { get; set; }

Related

await / async task not waiting

I am experiencing some confusion with Tasks and the async/await key words. I understand that you should NOT mix async and blocking code. Or at least what my interpretation of mixing them is:
Don't make calls to blocking API's from non- async methods. So here's my issue.
I am trying to await a method, then update the UI accordingly. The issue is that the only way to await an async method() call is from within and async method().
Here's an example:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
await RunValidation(options, cancellationTokenSource.Token);
Completed = true;
}));
}
}
This code runs the method properly and awaits. The issue is when I return. For some reason when setting the Complete flag the buttons dependent on this flag are not toggled. If I comment the await code, then the buttons are toggled correctly. So assumed I was not returning on the UI thread, so I tried using this code instead:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
var context = TaskScheduler.FromCurrentSynchronizationContext();
await RunValidation(options, cancellationTokenSource.Token).ContinueWith(t => Completed = true, context);
//Completed = true;
}));
}
}
Here is the RunValidation() method:
private async Task RunValidation(Options options, CancellationToken token)
{
await _someService.SomAsyncMethod(options, token));
}
If you notice, the ExecuteCommand has an async key word before the (options) parameter that is passed to the command. If I remove the async key word then I have to modify the call to the RunValidation() method. I still need it to await, so this is what I did:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>((options) =>
{
Completed = false;
var context = TaskScheduler.FromCurrentSynchronizationContext();
var cancellationTokenSource = new CancellationTokenSource();
Task.Run(async () => await RunValidation(options, cancellationTokenSource.Token));
Completed = true;
}));
}
}
The problem with this code is that it doesn't await. So I am at a loss.
Can anyone shed some light on this for me please. I've spend 2 plus days on this and I am still here.
Thanks,
Tim
Here are the bindings to the Command Buttons.
private readonly Independent<bool> _completed = new Independent<bool>(true);
public bool Completed
{
get { return _completed; }
set { _completed.Value = value; }
}
private ICommand _doneCommand;
public ICommand DoneCommand
{
get
{
return _doneCommand ?? (_doneCommand = MakeCommand.When(() => Completed).Do(() =>
{
DoSomething();
}));
}
}
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
return _cancelCommand ??
(_cancelCommand = MakeCommand.When(() => !Completed).Do(() => DoSomthingElse()));
}
}
I am using the MakeCommand objects from the UpdateControls library from Michael Perry. They contain dependancy tracking that raises the CanExecuteChange events when the Complete property is changed.
Your first code is correct. Most likely you have an incorrect implementation for your Completed property. Your view model object should implement INotifyPropertyChanged. The easiest way to do this right is use a base class that provides the functionality. ReactiveUI is the nuget package I always use. Usage is as simple as
public class MyObject : ReactiveObject {
private bool _Completed;
public bool Completed {
get => _Completed;
set => this.RaiseAndSetIfChanged(ref _Completed, value);
}
}
This will make sure that notifications are raised to the UI when the property is changed.
If you want it to be more magic you can use ReactiveUI.Fody and then your code will reduce to
public class MyObject : ReactiveObject {
[Reactive]
public bool Completed { get; set;}
}
So the issue was in fact the third party library. I was using it to provide dependency tracking for the 2 buttons. So when the complete flag changed it raised the CanExecuteChange events for both buttons without me having write code to do it. Unfortunately it stopped working after introducing the async/await calls. I replaced the 2 MakeCommands with RelayCommands and raised the events myself and everything worked.
So thanks to everyone for your responses.
Tim

C# UWP Async binding

I have a problem in UWP (Windows 10) with binding and async methods. I have a checkbox that is bind to one boolean isDay. When I change isDay, the checkbox is also changing its state.
My code in XAML looks like this:
IsChecked="{x:Bind isDay, Mode=TwoWay}"
When isDay is changed in async method, the checkbox is not changing its state.
What should I do to make this binding to work with async methods?
In my experience, the underlying cause of the checkbox not changing is the property change notification not firing on the UI thread. I believe UWP swallows the failure rather than throwing an exception about "accessing something from a different thread."
If you already have easy access to a Dispatcher, (your code-behind or another view-level component) then you would do something like:
private async void ClickHandler(object sender, EventArgs args)
{
bool checked = await SomeAsyncWorkThatReturnsBool().ConfigureAwait(false);
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => checkbox.IsChecked = checked);
}
But what if I don't have a Dispatcher?
More likely, you're asking because of async work in a view model, model, or another layer that doesn't actually have access to a Dispatcher (or the correct Dispatcher when there are more than one). In the past, I've used dependency injection and abstracted the interface to give my view models access to the UI thread, however this is arguably a code smell.
My current favorite approach (would love feedback) is to capture the SynchronizationContext during the event registration, and use it to post the change event.
public class BindableBase : INotifyPropertyChanged
{
private readonly List<(SynchronizationContext context, PropertyChangedEventHandler handler)> _handlers = new List<...>();
public event PropertyChangedEventHandler PropertyChanged
{
add => _handlers.Add((SynchronizationContext.Current, value));
remove
{
var i = 0;
foreach (var item in _handlers)
{
if (item.handler.Equals(value))
{
_handlers.RemoveAt(i);
break;
}
i++;
}
}
}
protected Task RaisePropertyChanged(string propertyName)
{
var args = new PropertyChangedEventArgs(propertyName);
var tasks = _handlers
.GroupBy(x => x.context, x => x.handler)
.Select(g => invokeContext(g.Key, g));
return Task.WhenAll(tasks);
Task invokeContext(SynchronizationContext context, IEnumerable<PropertyChangedEventHandler> l)
{
if (context != null)
{
var tcs = new TaskCompletionSource<bool>();
context.Post(o =>
{
try { invokeHandlers(l); tcs.TrySetResult(true); }
catch (Exception e) { tcs.TrySetException(e); }
}, null);
return tcs.Task;
}
else
{
return Task.Run(() => invokeHandlers(l));
}
}
void invokeHandlers(IEnumerable<PropertyChangedEventHandler> l)
{
foreach (var h in l)
h(this, args);
}
}
}
For some reason binding is not updating when values are updated via async methods. So, at the end of async method I just use:
Bindings.Update();
and controls are updated with latest values.
By default auto generated XAML code doesn't support awaitable methods, so you can't use Task<T> methods to bind in XAML.

Running an async method inside a relaycommand using MVVM

I'm developing an asynchronous application using WPF and MVVM, but I can't seem to get an async method to run inside my relaycommand.
I have a button on my WPF view hooked up to a relaycommand in my viewmodel, which trys to call an async method in my model to return a list of results:
/// <summary>
/// Search results
/// </summary>
private ObservableCollection<string> _searchResults = new ObservableCollection<string>();
public IList<string> SearchResults
{
get { return _searchResults; }
}
/// <summary>
/// Search button command
/// </summary>
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
_searchCommand = new RelayCommand(
async() =>
{
SearchResults.Clear();
var results = await DockFindModel.SearchAsync(_selectedSearchableLayer, _searchString);
foreach (var item in results)
{
SearchResults.Add(item);
}
//notify results have changed
NotifyPropertyChanged(() => SearchResults);
},
() => bAppRunning); //command will only execute if app is running
return _searchCommand;
}
}
However I get the following exception when the relaycommand tries to execute:
An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
Additional information: A Task's exception(s) were not observed either
by Waiting on the Task or accessing its Exception property. As a
result, the unobserved exception was rethrown by the finalizer thread.
I've tried a number of things in this thread to try and resolve the issue with no luck. Does anyone know how to resolve this?
Not sure where your RelayCommand is coming from (MVVM framework or custom implementation) but consider using an async version.
public class AsyncRelayCommand : ICommand
{
private readonly Func<object, Task> execute;
private readonly Func<object, bool> canExecute;
private long isExecuting;
public AsyncRelayCommand(Func<object, Task> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? (o => true);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(object parameter)
{
if (Interlocked.Read(ref isExecuting) != 0)
return false;
return canExecute(parameter);
}
public async void Execute(object parameter)
{
Interlocked.Exchange(ref isExecuting, 1);
RaiseCanExecuteChanged();
try
{
await execute(parameter);
}
finally
{
Interlocked.Exchange(ref isExecuting, 0);
RaiseCanExecuteChanged();
}
}
}
Ok so I actually managed to resolve the issue by making a change to the way I run my async method.
I changed this:
var results = await DockFindModel.SearchAsync();
To this:
var results = await QueuedTask.Run(() => DockFindModel.SearchAsync());
Although i'm a bit confused as to why I need to await Task.Run() when relaycommand is already accepting async lambda. I'm sure it'll become clear with time however.
Thanks to those who commented.
That RelayCommand class is probably accepting a parameter of type Action. Since you are passing an async lambda, we are having an async void scenario here.
Your code will be fired and forgot. Consider using an ICommand implementation that takes Func<Task> as a parameter instead of Action.

ReactiveUI OneWayBind leaks handles

I have a simple ViewModel:
public class MeetingPageViewModel : ReactiveObject, IRoutableViewModel
{
public MeetingPageViewModel(IScreen hs, IMeetingRef mRef)
{
HostScreen = hs;
_backing = "hi there";
}
public IScreen HostScreen { get; private set; }
public string MeetingTitle
{
get { return _backing; }
set { this.RaiseAndSetIfChanged(ref _backing, value); }
}
string _backing;
public string UrlPathSegment
{
get { return "/meeting"; }
}
}
And I bind to the MeetingTitle a TextBlock:
public sealed partial class MeetingPage : Page, IViewFor<MeetingPageViewModel>
{
public MeetingPage()
{
this.InitializeComponent();
// Bind everything together we need.
this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text);
}
/// <summary>
/// Stash the view model
/// </summary>
public MeetingPageViewModel ViewModel
{
get { return (MeetingPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MeetingPageViewModel), typeof(MeetingPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MeetingPageViewModel)value; }
}
}
After navigating back to the previous screen MeetingPageViewModel isn't garbage collected. I'm using 6.4.1 of RxUI and VS2013 (and the memory analysis analyze tool). If I dispose of the return value from OneWayBind then everything is cleaned up properly - but of course, I no longer have the bindings.
It turns out the problem is the DependencyProperty ViewModel. Its lifetime is forever (note the "static" in its declaration). As a result, the binding that is attached to it is never garbage collected, since it never goes away, and that binding then holds a reference to both the view and viewmodel, and so they never go away.
The only way to break this is explicitly clean up the bindings. RxUI provides the WhenActivated method to help with this. Surround the bindings in a lambda, and use the provided function to track the IDisposals. When the view goes away this will then be cleaned up.
this.WhenActivated(disposeOfMe => {
disposeOfMe (this.OneWayBind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text));
});

How to call an async method from a getter or setter?

What'd be the most elegant way to call an async method from a getter or setter in C#?
Here's some pseudo-code to help explain myself.
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron.
Properties should return current values; they should not be kicking off background operations.
Usually, when someone wants an "asynchronous property", what they really want is one of these:
An asynchronous method that returns a value. In this case, change the property to an async method.
A value that can be used in data-binding but must be calculated/retrieved asynchronously. In this case, either use an async factory method for the containing object or use an async InitAsync() method. The data-bound value will be default(T) until the value is calculated/retrieved.
A value that is expensive to create, but should be cached for future use. In this case, use AsyncLazy from my blog or AsyncEx library. This will give you an awaitable property.
Update: I cover asynchronous properties in one of my recent "async OOP" blog posts.
You can't call it asynchronously, since there is no asynchronous property support, only async methods. As such, there are two options, both taking advantage of the fact that asynchronous methods in the CTP are really just a method that returns Task<T> or Task:
// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
get
{
// Just call the method
return MyAsyncMethod();
}
}
Or:
// Make the property blocking
public IEnumerable MyList
{
get
{
// Block via .Result
return MyAsyncMethod().Result;
}
}
I really needed the call to originate from the get method, due to my decoupled architecture. So I came up with the following implementation.
Usage: Title is in a ViewModel or an object you could statically declare as a page resource. Bind to it and the value will get populated without blocking the UI, when getTitle() returns.
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can use Task like this :
public int SelectedTab
{
get => selected_tab;
set
{
selected_tab = value;
new Task(async () =>
{
await newTab.ScaleTo(0.8);
}).Start();
}
}
I think that we can await for the value just returning first null and then get the real value, so in the case of Pure MVVM (PCL project for instance) I think the following is the most elegant solution:
private IEnumerable myList;
public IEnumerable MyList
{
get
{
if(myList == null)
InitializeMyList();
return myList;
}
set
{
myList = value;
NotifyPropertyChanged();
}
}
private async void InitializeMyList()
{
MyList = await AzureService.GetMyList();
}
I thought .GetAwaiter().GetResult() was exactly the solution to this problem, no?
eg:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
_Title = getTitle().GetAwaiter().GetResult();
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Since your "async property" is in a viewmodel, you could use AsyncMVVM:
class MyViewModel : AsyncBindableBase
{
public string Title
{
get
{
return Property.Get(GetTitleAsync);
}
}
private async Task<string> GetTitleAsync()
{
//...
}
}
It will take care of the synchronization context and property change notification for you.
You can create an event and invoke an event when the property is changed.
Something like this:
private event EventHandler<string> AddressChanged;
public YourClassConstructor(){
AddressChanged += GoogleAddressesViewModel_AddressChanged;
}
private async void GoogleAddressesViewModel_AddressChanged(object sender, string e){
... make your async call
}
private string _addressToSearch;
public string AddressToSearch
{
get { return _addressToSearch; }
set
{
_addressToSearch = value;
AddressChanged.Invoke(this, AddressToSearch);
}
}
When I ran into this problem, trying to run an async method synchronicity from either a setter or a constructor got me into a deadlock on the UI thread, and using an event handler required too many changes in the general design.
The solution was, as often is, to just write explicitly what I wanted to happen implicitly, which was to have another thread handle the operation and to get the main thread to wait for it to finish:
string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();
You could argue that I abuse the framework, but it works.
Necromancing.
In .NET Core/NetStandard2, you can use Nito.AsyncEx.AsyncContext.Run instead of System.Windows.Threading.Dispatcher.InvokeAsync:
class AsyncPropertyTest
{
private static async System.Threading.Tasks.Task<int> GetInt(string text)
{
await System.Threading.Tasks.Task.Delay(2000);
System.Threading.Thread.Sleep(2000);
return int.Parse(text);
}
public static int MyProperty
{
get
{
int x = 0;
// https://stackoverflow.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
// https://stackoverflow.com/questions/41748335/net-dispatcher-for-net-core
// https://github.com/StephenCleary/AsyncEx
Nito.AsyncEx.AsyncContext.Run(async delegate ()
{
x = await GetInt("123");
});
return x;
}
}
public static void Test()
{
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
System.Console.WriteLine(MyProperty);
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
}
}
If you simply chose System.Threading.Tasks.Task.Run or System.Threading.Tasks.Task<int>.Run, then it wouldn't work.
I think my example below may follow #Stephen-Cleary 's approach but I wanted to give a coded example. This is for use in a data binding context for example Xamarin.
The constructor of the class - or indeed the setter of another property on which it is dependent - may call an async void that will populate the property on completion of the task without the need for an await or block. When it finally gets a value it will update your UI via the NotifyPropertyChanged mechanism.
I'm not certain about any side effects of calling a aysnc void from a constructor. Perhaps a commenter will elaborate on error handling etc.
class MainPageViewModel : INotifyPropertyChanged
{
IEnumerable myList;
public event PropertyChangedEventHandler PropertyChanged;
public MainPageViewModel()
{
MyAsyncMethod()
}
public IEnumerable MyList
{
set
{
if (myList != value)
{
myList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
get
{
return myList;
}
}
async void MyAsyncMethod()
{
MyList = await DoSomethingAsync();
}
}
I review all answer but all have a performance issue.
for example in :
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
use dispatcher which is not a good answer.
but there is a simple solution, just do it:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Task.Run(()=>
{
_Title = getTitle();
RaisePropertyChanged("Title");
});
return;
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can change the proerty to Task<IEnumerable>
and do something like:
get
{
Task<IEnumerable>.Run(async()=>{
return await getMyList();
});
}
and use it like
await MyList;

Categories

Resources