C# UWP Async binding - c#

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.

Related

Property changed is not updating the UI from inside a task

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

uwp - 'The application called an interface that was marshalled for a different thread

I'm trying to create UWP chat using MVVM. I keep getting this error:
The application called an interface that was marshalled for a different thread
in my view model in here:
public void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
this function occurs every time a value is set to the string property that bond to the TextBlock.Text property in the xaml;
i tried inserting the TextBlock (that the vm was bond to its text property) in the view model and remove all the binding in the xaml and use this function:
private async Task LoadMessage(string sender, string message)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatBox += $"{sender}: {message} \n";
});
}
still the same, only now the exception is thrown in the above function.
I found anther dispatcher while searching for answer but it seems that uwp doesn't recognize it and mark it with red under-line:
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//my code
});
I tried :
await CoreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatBox += $"{sender}: {message} \n";
});
but then i got
an object reference is required for the non-static field, method
so I created a static class the holds this function :
public static void UpdateTextBlock(TextBlock textBlock, string sender, string message)
{
textBlock.Text = $"{sender}: {message}";
}
and inserting it to this dispatcher. still no-go. still: an object reference is required for...
i really want it to be MVVM, but any solution that works would be a bless.
EDIT
today i tried moving back to the binding and mvvm pattern. i was wrapping the LoadMessage function in task like this:
private async Task<bool> LoadMessage(string sender, string message)
{
bool flag = false;
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TextBindingProperty += $"{sender}: {message} \n";
flag = true;
});
return flag;
}
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await Task.Run(() => LoadMessage(sender, message));
});
same exception only in class : MyView.g.cs
in this method:
public static void Set_Windows_UI_Xaml_Controls_TextBlock_Text(global::Windows.UI.Xaml.Controls.TextBlock obj, global::System.String value, string targetNullValue)
{
if (value == null && targetNullValue != null)
{
value = targetNullValue;
}
obj.Text = value ?? global::System.String.Empty; //in this line!!!
}
};
My view model implement INotifyPropertyChanged
public class UserViewModel : INotifyPropertyChanged
{
private string chatBox;
public string ChatBox
{
get { return chatBox; }
set { chatBox = value; Notify(nameof(ChatBox)); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
the xaml element that is bond to this property looks like this:
<TextBlock Name="chatTbl" Text="{x:Bind userViewModel.ChatBox, Mode=TwoWay}" />
Solution:
so battling to make the chat work this was the problem:
await CoreApplication.MainView
since the chat is in a different view than the main page it couldn't find the binding or the xaml element.
what my friend realize that in order to make changes in other view you need to get its object so, we added this property to the view model:
public CoreApplicationView CurrentView { get; set; }
when launching the chat room view we set the property:
public UserViewModel userViewModel;
public ChatRoom(UserViewModel userViewModel)
{
this.InitializeComponent();
this.userViewModel = userViewModel;
userViewModel.ChatTbl = chatTbl;
userViewModel.CurrentView = CoreApplication.GetCurrentView();//this line
}
now when we need to make changes in the view we use:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
await CurrentView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatTbl.Text += $"{sender}: {message} \n";
});
});
so instead await CoreApplication.MainView
use await CurrentView.CoreWindow
hope this would help someone i know it would have helped me
Your LoadMessage is an asynchronous method, you did not need to put it in Task.Run. You could directly call it like the following:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await LoadMessage(sender, message);
});
If you have to put it in Task.Run, you could call it like this:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await Task.Run(async() => await LoadMessage(sender, message));
});

Close task before run again

I working on real-time search. At this moment on property setter which is bounded to edit text, I call a method which calls API and then fills the list with the result it looks like this:
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
RunInAsync(LoadData);
}
}
private async Task LoadData()
{
var response = await connectionRepository.GetConnections(currentPage,
pageSize, searchPhrase);
foreach (UserConnection uc in response)
{
if (uc.Type != UserConnection.TypeEnum.Awaiting)
{
RunOnMainThread(() =>
SearchResult.Add(new ConnectionUser(uc)));
}
}
}
But this way is totally useless because of it totally mashup list of a result if a text is entering quickly. So to prevent this I want to run this method async in a property but if a property is changed again I want to kill the previous Task and star it again. How can I achieve this?
Some informations from this thread:
create a CancellationTokenSource
var ctc = new CancellationTokenSource();
create a method doing the async work
private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
return Task.Run(() =>
{
token.ThrowIfCancellationRequested();
// more code here
// check again if this task is canceled
token.ThrowIfCancellationRequested();
// more code
}
}
It is important to have this checks for cancel in the code.
Execute the function:
var cancellable = ExecuteLongCancellableMethod(ctc.Token);
To stop the long running execution use
ctc.Cancel();
For further details please consult the linked thread.
This question can be answered in many different ways. However IMO I would look at creating a class that
Delays itself automatically for X (ms) before performing the seach
Has the ability to be cancelled at any time as the search request changes.
Realistically this will change your code design, and should encapsulate the logic for both 1 & 2 in a separate class.
My initial thoughts are (and none of this is tested and mostly pseudo code).
class ConnectionSearch
{
public ConnectionSearch(string phrase, Action<object> addAction)
{
_searchPhrase = phrase;
_addAction = addAction;
_cancelSource = new CancellationTokenSource();
}
readonly string _searchPhrase = null;
readonly Action<object> _addAction;
readonly CancellationTokenSource _cancelSource;
public void Cancel()
{
_cancelSource?.Cancel();
}
public async void PerformSearch()
{
await Task.Delay(300); //await 300ms between keystrokes
if (_cancelSource.IsCancellationRequested)
return;
//continue your code keep checking for
//loop your dataset
//call _addAction?.Invoke(uc);
}
}
This is basic, really just encapsulates the logic for both points 1 & 2, you will need to adapt the code to do the search.
Next you could change your property to cancel a previous running instance, and then start another instance immediatly after something like below.
ConnectionSearch connectionSearch;
string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
//do your setter work
if(connectionSearch != null)
{
connectionSearch.Cancel();
}
connectionSearch = new ConnectionSearch(value, addConnectionUser);
connectionSearch.PerformSearch();
}
}
void addConnectionUser(object uc)
{
//pperform your add logic..
}
The code is pretty straight forward, however you will see in the setter is simply cancelling an existing request and then creating a new request. You could put some disposal cleanup logic in place but this should get you started.
You can implement some sort of debouncer which will encapsulate the logics of task result debouncing, i.e. it will assure if you run many tasks, then only the latest task result will be used:
public class TaskDebouncer<TResult>
{
public delegate void TaskDebouncerHandler(TResult result, object sender);
public event TaskDebouncerHandler OnCompleted;
public event TaskDebouncerHandler OnDebounced;
private Task _lastTask;
private object _lock = new object();
public void Run(Task<TResult> task)
{
lock (_lock)
{
_lastTask = task;
}
task.ContinueWith(t =>
{
if (t.IsFaulted)
throw t.Exception;
lock (_lock)
{
if (_lastTask == task)
{
OnCompleted?.Invoke(t.Result, this);
}
else
{
OnDebounced?.Invoke(t.Result, this);
}
}
});
}
public async Task WaitLast()
{
await _lastTask;
}
}
Then, you can just do:
private readonly TaskDebouncer<Connections[]> _connectionsDebouncer = new TaskDebouncer<Connections[]>();
public ClassName()
{
_connectionsDebouncer.OnCompleted += OnConnectionUpdate;
}
public void OnConnectionUpdate(Connections[] connections, object sender)
{
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
foreach (var conn in connections)
RunOnMainThread(() => SearchResult.Add(new ConnectionUser(conn)));
}
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
_connectionsDebouncer.Add(RunInAsync(LoadData));
}
}
private async Task<Connection[]> LoadData()
{
return await connectionRepository
.GetConnections(currentPage, pageSize, searchPhrase)
.Where(conn => conn.Type != UserConnection.TypeEnum.Awaiting)
.ToArray();
}
It is not pretty clear what RunInAsync and RunOnMainThread methods are.
I guess, you don't actually need them.

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

Enabling EAP and async/await on same class

I'm trying create a class that has events AND can be awaitable, but keep coming across stumbling blocks.
First, I tried a TransferJob class that returns a TransferTask object which is already running when it is returned. This would be accomplished through something like this:
public abstract class TransferJob
{
public TransferTask Start()
{
return Start(CancellationToken.None);
}
public TransferTask Start(CancellationToken token)
{
TransferTask task = CreateTransferTask();
task.Start(token);
return task;
}
protected abstract TransferTask CreateTransferTask();
}
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private Task transferTask;
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
internal void Start(CancellationToken token)
{
this.transferTask = TransferAsync(cancellationToken);
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.GetAwaiter();
}
}
The problem with the above is that if the TransferTask finishes very quickly, then users of TransferJob.Start() might not have time to register their event handlers on the returned TransferTask's StatusChanged event before it finishes. So I tried a different approach whereby the user has to call the TransferTask's Start() method themselves. This would give the user time to register their event handlers on the TransferTask in between the transferJob.CreateTask() call and the transferTask.Start() call:
public abstract class TransferJob
{
public abstract TransferTask CreateTask();
}
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private Task transferTask;
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
public void Start(CancellationToken token)
{
this.transferTask = TransferAsync(cancellationToken);
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.GetAwaiter();
}
}
Now, I have a different problem. If a user tries await transferTask; before transferTask.Start(); has been called, then presumably they'll get a NullReferenceException thrown because the task hasn't been started (and therefore assigned to the transferTask field). I'm really struggling for a way to solve this. Is there a way? Or a better pattern to use than the above?
I'm not really convinced this is a good idea. Just expose the TAP pattern. Delete the event as well as transferTask. The caller of Start must hold onto that task and pass it to any code that wants to listen for completion. This results in a very clean API. No mutable state, very simple to understand, supports all use cases.
If you insist, you can create a proxy task that looks like it's the real thing:
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private TaskCompletionSource<object> transferTask = new ...; //changed
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
public Task Start(CancellationToken token)
{
await TransferAsync(cancellationToken);
transferTask.SetResult(null); //complete proxy task
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.Task.GetAwaiter(); //changed
}
}
Now, transferTask.Task is always not null. That task will eventually complete. I quickly hacked this together, I hope the idea is clear.
Probably, you should base the event on transferTask.Task.ContinueWith(...).
The best way I found when trying to mix events and awaitable code in C# is to use the Reactive Extension (Rx) library. From Microsoft:
Reactive Extension (Rx) is a library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators.
You could do something like the following to fix your issue. (I am not sure this is exactly what you want to accomplish, but the goal is just to demonstrate how Rx can be used to combine events with asynchronous code):
public async Task TransferAndWaitStartedAsync()
{
var transferTask = new TransferTask();
// Prepare the observable before executing the transfer to make sure that the observable sequence will receive the event
// You can use Linq operators to filter only specific events. In this case, I only care about events with Status == StatusCode.Started
var whenStatusChanged = Observable.FromEventPattern<TransferStatusChangedEventArgs>(h, transferTask.StatusChanged += h, h => transferTask.StatusChanged -= h)
.Where(e => e.EventArgs.Status == StatusCode.Started)
.FirstAsync();
// Start the transfer asynchronously
await transferTask.TransferAsync();
// Continuation will complete when receiving the first event that matches the predicate in the observable sequence even if the event was triggered too quickly.
await whenStatusChanged;
}
I find that the Rx library has a steep learning curve with all its subtleties, but when you know how to use it, it is a really powerful tool.
Intro to Rx with lot of examples
Design guidelines

Categories

Resources