I have never taken the time to understand Async and Await modifiers. There are a few places in the application I'm currently working on where the application becomes non-responsive for a few seconds and I'd like to see if I can fix that. Async and Await are obvious candidates.
I wrote a test program that has four features.
- Calling a method synchronously
- Calling a method asynchronously.
- Calling a synchronous method from an asynchronous method
- Calling an asynchronous method from a synchronous method.
<Window x:Class="AsyncAwait.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AsyncAwait"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="SyncCommand"/>
<RoutedCommand x:Key="AsyncCommand"/>
<RoutedCommand x:Key="SyncToAsyncCommand"/>
<RoutedCommand x:Key="AsyncToSyncCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource SyncCommand}" Executed="SyncExecuted"/>
<CommandBinding Command="{StaticResource AsyncCommand}" Executed="AsyncExecuted"/>
<CommandBinding Command="{StaticResource SyncToAsyncCommand}" Executed="SyncToAsyncExecuted"/>
<CommandBinding Command="{StaticResource AsyncToSyncCommand}" Executed="AsyncToSyncExecuted"/>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Sync" Command="{StaticResource SyncCommand}"/>
<Button Grid.Row="1" Content="ASync" Command="{StaticResource AsyncCommand}"/>
<Button Grid.Row="2" Content="Sync to Async" Command="{StaticResource SyncToAsyncCommand}"/>
<Button Grid.Row="3" Content="Async to Sync" Command="{StaticResource AsyncToSyncCommand}"/>
<ListView Grid.Row="4" ItemsSource="{Binding Items}"/>
</Grid>
</Window>
namespace AsyncAwait
[ObservableObject]
public partial class MainWindow : Window
[ObservableProperty]
private ObservableCollection<string> items = new ObservableCollection<string>();
public MainWindow()
InitializeComponent();
}
private void InitItems()
Items.Clear();
AppendItem("InitItems");
private async Task InitItemsAsync()
await Application.Current.Dispatcher.BeginInvoke(() => Items.Clear());
private void AppendItem(string Item)
string s = $"{System.DateTime.Now.ToString("HH:mm:ss.fff")} {Item}";
Debug.WriteLine(s);
}
private async Task AppendItemAsync(string Item)
string s = $"{System.DateTime.Now.ToString("HH:mm:ss.fff")} {Item}";
}
private void SyncExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
InitItems();
AppendItem("SyncExecuted");
AppendItem("SyncExecuted Exit");
private void SyncExecuted2()
AppendItem("SyncExecuted2");
AppendItem("SyncExecuted2 Exit");
private async void AsyncExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
await InitItemsAsync();
private async Task AsyncExecuted2()
await AppendItemAsync("AsyncExecuted2");
private void SyncToAsyncExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
InitItems();
AppendItem("SyncToAsyncExecuted");
AppendItem("SyncToAsyncExecuted Exit");
private async void SyncToAsyncExecuted2()
await AppendItemAsync("SyncToAsyncExecuted2");
private async void AsyncToSyncExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
await InitItemsAsync();
private void AsyncToSyncExecuted2()
AppendItem("AsyncToSyncExecuted2");
AppendItem("AsyncToSyncExecuted2 Exit");
}
}
}
Run the program and click the [Sync] button. Try moving the window while the code is running.
You will notice you cannot move the window while the method is executing and the listbox is only populated once the SyncExecuted method has finished. This is the behavior we are trying to avoid.
Now click the [Async] button and try to move the window.
You can move the window while the AsyncExecuted method is executing and the results are shown as the code executes. The is the behavior we want.
The first thing to notice is the AsyncExecuted event handler is marked async void which is how you call an async method from a non-async method. AsyncExecuted calls async versions of everything. This means they are running on non-UI threads. The Items collection is owned by the UI thread which means we have to use Dispatcher.BeginInvoke to update the collection. If we don't use BeginInvoke we will get a run-time exception.
So it looks like it's fairly easy to add some Async and Await operators and solve our responsiveness problem. But there are some wrinkles...
One of the problems with Async and Await is that you can only call Await from an Async method and you can only call an Async method with the Await modifier. This forces us to modify our entire call stack if we want one method in it to run asynchronously. We need to figure out how to call an async method from a non-async method and vice versa.
The third button calls a non-async event handler called SyncToAsyncExecuted and this method calls the async method SyncToAsyncExecuted2. If you click the third button, take a close look at the output.
So although we are allowed to call an Async method without the Await operator, we must be aware that the calling code with continue immediately. Sometimes this is what we want, sometimes it is not.
No comments:
Post a Comment