Skip to content

Instantly share code, notes, and snippets.

@Athari
Created July 26, 2016 19:06
Show Gist options
  • Save Athari/201775c28e2d549a0c01b936e82de532 to your computer and use it in GitHub Desktop.
Save Athari/201775c28e2d549a0c01b936e82de532 to your computer and use it in GitHub Desktop.
AwaitableCommandExample by Vlad@RuSO
using System;
using System.Threading;
using System.Windows;
using System.Windows.Input;
namespace AwaitableCommandExample
{
public partial class App
{
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
using (var appFlowCancellation = new CancellationTokenSource())
{
CommandManager.RegisterClassCommandBinding(typeof(Window),
new CommandBinding(ApplicationCommands.Close, (o, args) => appFlowCancellation.Cancel()));
var mainVM = new MainVM();
var mainWindow = new MainWindow() { DataContext = mainVM };
mainWindow.Show();
try
{
await mainVM.Run(appFlowCancellation.Token);
}
catch (OperationCanceledException) when (appFlowCancellation.IsCancellationRequested)
{ }
mainWindow.Close();
}
}
}
}
using System;
using System.Threading.Tasks;
using System.Windows.Input;
namespace AwaitableCommandExample
{
class AwaitableCommand : VM, ICommand
{
readonly TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
public Task Task => tcs.Task;
public event EventHandler CanExecuteChanged;
bool canExecute = true;
public bool CanExecute
{
get { return canExecute; }
set { if (canExecute != value) { canExecute = value; Raise(); } }
}
void Raise()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
RaisePropertyChanged(nameof(CanExecute));
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute;
}
void ICommand.Execute(object parameter)
{
if (!CanExecute)
return;
tcs.TrySetResult(0);
}
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AwaitableCommandExample
{
class ExecutionVM : VM
{
public ExecutionVM(ParameterVM parameterVM)
{
ParameterVM = parameterVM;
}
public ParameterVM ParameterVM { get; }
public AwaitableCommand Finish { get; } = new AwaitableCommand();
public AwaitableCommand Cancel { get; } = new AwaitableCommand();
string status;
public string Status
{
get { return status; }
set { if (status != value) { status = value; RaisePropertyChanged(); } }
}
public async Task Run(CancellationToken ct)
{
Finish.CanExecute = false;
var breakCts = new CancellationTokenSource();
try
{
using (var commonCt = CancellationTokenSource.CreateLinkedTokenSource(ct, breakCts.Token))
{
var workerTask = WorkerTask(commonCt.Token, new Progress<string>(s => Status = s));
var winner = await Task.WhenAny(workerTask, Cancel.Task);
if (winner == Cancel.Task)
breakCts.Cancel();
await workerTask;
}
}
catch (OperationCanceledException) when (breakCts.IsCancellationRequested)
{ }
Finish.CanExecute = true;
Cancel.CanExecute = false;
using (var cancelTask = ct.ThrowWhenCanceled())
await await Task.WhenAny(Finish.Task, cancelTask.ToTask());
}
async Task WorkerTask(CancellationToken ct, IProgress<string> statusProgress)
{
var from = ParameterVM.FromPath;
var to = ParameterVM.ToPath;
// here is call to model
for (double progress = 0; progress < 1.0; progress = Math.Min(progress + 0.33333333, 1.0))
{
statusProgress.Report($"Copying {from} to {to} ({progress:P} ready)");
await Task.Delay(1000, ct);
}
statusProgress.Report($"Copying {from} to {to} (100% ready)");
}
}
}
using System.Threading;
using System.Threading.Tasks;
namespace AwaitableCommandExample
{
class MainVM : VM
{
readonly ParameterVM parameterVM = new ParameterVM();
readonly ParameterEditorVM parameterInputVM;
readonly ExecutionVM executionVM;
public MainVM()
{
parameterInputVM = new ParameterEditorVM(parameterVM);
executionVM = new ExecutionVM(parameterVM);
}
public async Task Run(CancellationToken ct)
{
CurrentVM = parameterInputVM;
var inputResult = await parameterInputVM.Run(ct);
if (!inputResult)
return;
CurrentVM = executionVM;
await executionVM.Run(ct);
CurrentVM = null;
}
VM currentVM;
public VM CurrentVM
{
get { return currentVM; }
set { if (currentVM != value) { currentVM = value; RaisePropertyChanged(); } }
}
}
}
<Window x:Class="AwaitableCommandExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AwaitableCommandExample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ParameterEditorVM}">
<Grid d:DataContext="{d:DesignInstance Type=local:ParameterEditorVM, IsDesignTimeCreatable=True}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DockPanel LastChildFill="True" Grid.Row="0">
<Label DockPanel.Dock="Left" Target="{Binding ElementName=From}">Enter source path:</Label>
<TextBox Name="From" Text="{Binding ParameterVM.FromPath}"/>
</DockPanel>
<DockPanel LastChildFill="True" Grid.Row="1">
<Label DockPanel.Dock="Left" Target="{Binding ElementName=To}">Enter destination path:</Label>
<TextBox Name="To" Text="{Binding ParameterVM.ToPath}"/>
</DockPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3" HorizontalAlignment="Right">
<Button Command="{Binding Cancel}" MinWidth="75" Margin="10,10,0,10">Cancel</Button>
<Button Command="{Binding Finish}" MinWidth="75" Margin="10">Next</Button>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ExecutionVM}">
<Grid d:DataContext="{d:DesignInstance Type=local:ExecutionVM, IsDesignTimeCreatable=True}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Status}"/>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
<Button Command="{Binding Cancel}" MinWidth="75" Margin="10,10,0,10">Cancel</Button>
<Button Command="{Binding Finish}" MinWidth="75" Margin="10">Next</Button>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="Close"/>
</MenuItem>
</Menu>
<ContentPresenter Grid.Row="1" Content="{Binding CurrentVM}"/>
</Grid>
</Window>
using System.Threading;
using System.Threading.Tasks;
namespace AwaitableCommandExample
{
class ParameterEditorVM : VM
{
public ParameterEditorVM(ParameterVM parameterVM)
{
ParameterVM = parameterVM;
}
public ParameterVM ParameterVM { get; }
public AwaitableCommand Finish { get; } = new AwaitableCommand();
public AwaitableCommand Cancel { get; } = new AwaitableCommand();
public async Task<bool> Run(CancellationToken ct)
{
using (var cancelRegistration = ct.ThrowWhenCanceled())
{
var winner = await Task.WhenAny(Finish.Task, Cancel.Task, cancelRegistration.ToTask());
await winner;
return winner == Finish.Task;
}
}
}
}
namespace AwaitableCommandExample
{
class ParameterVM : VM
{
string fromPath = @"from\file", toPath = @"to\file";
public string FromPath
{
get { return fromPath; }
set { if (fromPath != value) { fromPath = value; RaisePropertyChanged(); } }
}
public string ToPath
{
get { return toPath; }
set { if (toPath != value) { toPath = value; RaisePropertyChanged(); } }
}
}
}
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace AwaitableCommandExample
{
static class Tools
{
public class CancellationDisposableAwaitable : IDisposable
{
public CancellationDisposableAwaitable(CancellationToken cancellationToken)
{
registration = cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
}
public async Task ToTask() => await this;
public TaskAwaiter<bool> GetAwaiter() => tcs.Task.GetAwaiter();
public void Dispose() => registration.Dispose();
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
CancellationTokenRegistration registration;
}
public class ThrowingCancellationDisposableAwaitable : IDisposable
{
public ThrowingCancellationDisposableAwaitable(CancellationToken cancellationToken)
{
registration = cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetCanceled(), tcs);
}
public async Task ToTask() => await this;
public TaskAwaiter<bool> GetAwaiter() => tcs.Task.GetAwaiter();
public void Dispose() => registration.Dispose();
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
CancellationTokenRegistration registration;
}
public static CancellationDisposableAwaitable WhenCanceled(this CancellationToken cancellationToken)
{
return new CancellationDisposableAwaitable(cancellationToken);
}
public static ThrowingCancellationDisposableAwaitable ThrowWhenCanceled(this CancellationToken cancellationToken)
{
return new ThrowingCancellationDisposableAwaitable(cancellationToken);
}
}
}
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace AwaitableCommandExample
{
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment