Недавно, я в гневе писал вот эту статью, про то, что методы помеченные как async ведут себя не так, как от них ожидается. Давайте, сегодня я покажу небольшой пример на то, как вернуть им возможность выполниться как синхронным.
Итак, давайте возьмем простенький пример с загрузкой изображений. Для этого, я накидал небольшую формочку вот с такой разметкой:
<RowDefinition Height="auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Margin="5,5,100,5" x:Name="tbURI" Grid.ColumnSpan="2" />
<Button HorizontalAlignment="Right" Margin="5" Width="90" Content="Скачать" Grid.ColumnSpan="2" Click="Button_Click" />
<TextBox Margin="5" IsReadOnly="True" x:Name="tbLog" Grid.Row="1" TextWrapping="Wrap" />
<Image Margin="5" x:Name="imView" Grid.Row="1" Grid.Column="1" Stretch="UniformToFill" />
</Grid>
Ну и под все это вот такой код:
public MainWindow()
{
InitializeComponent();
}
Dispatcher.Invoke((Action)(
() => tbLog.Text += string.Format("{0:hh:mm:ss} - {1}\n", DateTime.Now, p_message)
));
}
WriteLog("Начало скачивания");
DownloadImage();
WriteLog("Окончание скачивания");
}
byte[] buffer = (new WebClient()).DownloadData(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
}
Все достаточно просто. Вводим URL, тыкаем кнопку и видим картинку. Плюс, у нас есть возможность смотреть порядок выполнения действий выполняемых программой:
Пока все в последовательности логично. Единственно, что раздражает в нашей программе, это "зависание" при скачивании большой картинки. Обратите внимание на время между началом работы обработчика кнопки и окончанием.
К счастью, о нас уже подумали и дали нам возможность воспользоваться вместо метода DownloadData его асинхронным собратом DownloadDataTaskAsync. Правим код:
byte[] buffer = await (new WebClient()).DownloadDataTaskAsync(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
Не привожу метод обработчик клика на кнопке, т.к. он не поменялся, т.е. в нем вызов DownloadImage по прежнему выглядит как синхронный.
Запускаем:
Приложение больше не "зависает", но у него изменился порядок вывода. Теперь, приложение считает, что скачивание завершилось мгновенно. И если у на вместо вывода в лог этого радостного события будет обработка загруженного изображения... Ну вы поняли. Ничего хорошего не будет.
Как с этим бороться? С одной стороны достаточно легко, с другой, мы получив проблему на одном уровне, просто транслируем ее на уровень выше (в данном случае это нам ничем не грозит, а вот в других случаях, возможно описанное решение придется применять и дальше).
Теперь придется править не только код метода отвечающего за скачивание, но и метод его вызывающий:
WriteLog("Начало скачивания");
await DownloadImageAsync();
WriteLog("Окончание скачивания");
}
byte[] buffer = await (new WebClient()).DownloadDataTaskAsync(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
Как видно, изменений три:
1. Изменен тип возвращаемого значения у скачивающего картинку метода с void на Task.
2. К имени этого метода добавлен суффикс Async (рекомендуют так делать, чтобы эти методы были легко узнаваемыми).
3. Обработчик клика на кнопке сам стал async.
Все, теперь все работает как и ожидалось:
На этом можно было бы и закончить рассказ, если бы не одно но, про которое должен был возникнуть вопрос при взгляде на метод DownloadImageAsync. У него изменился тип возвращаемого значения с void на Task, а вот строчки return, у него как не было, так и нет. Это еще один "синтаксический сахар", который появился в c# 5. Ну а теперь, действительно все.
Итак, давайте возьмем простенький пример с загрузкой изображений. Для этого, я накидал небольшую формочку вот с такой разметкой:
<Grid>
<Grid.RowDefinitions><RowDefinition Height="auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Margin="5,5,100,5" x:Name="tbURI" Grid.ColumnSpan="2" />
<Button HorizontalAlignment="Right" Margin="5" Width="90" Content="Скачать" Grid.ColumnSpan="2" Click="Button_Click" />
<TextBox Margin="5" IsReadOnly="True" x:Name="tbLog" Grid.Row="1" TextWrapping="Wrap" />
<Image Margin="5" x:Name="imView" Grid.Row="1" Grid.Column="1" Stretch="UniformToFill" />
</Grid>
Ну и под все это вот такой код:
public
partial class MainWindow : Window
{public MainWindow()
{
InitializeComponent();
}
void
WriteLog(string p_message)
{Dispatcher.Invoke((Action)(
() => tbLog.Text += string.Format("{0:hh:mm:ss} - {1}\n", DateTime.Now, p_message)
));
}
private void Button_Click(object
sender, RoutedEventArgs e)
{WriteLog("Начало скачивания");
DownloadImage();
WriteLog("Окончание скачивания");
}
private void DownloadImage()
{byte[] buffer = (new WebClient()).DownloadData(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
}
Все достаточно просто. Вводим URL, тыкаем кнопку и видим картинку. Плюс, у нас есть возможность смотреть порядок выполнения действий выполняемых программой:
Пока все в последовательности логично. Единственно, что раздражает в нашей программе, это "зависание" при скачивании большой картинки. Обратите внимание на время между началом работы обработчика кнопки и окончанием.
К счастью, о нас уже подумали и дали нам возможность воспользоваться вместо метода DownloadData его асинхронным собратом DownloadDataTaskAsync. Правим код:
private async void
DownloadImage()
{byte[] buffer = await (new WebClient()).DownloadDataTaskAsync(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
Не привожу метод обработчик клика на кнопке, т.к. он не поменялся, т.е. в нем вызов DownloadImage по прежнему выглядит как синхронный.
Запускаем:
Приложение больше не "зависает", но у него изменился порядок вывода. Теперь, приложение считает, что скачивание завершилось мгновенно. И если у на вместо вывода в лог этого радостного события будет обработка загруженного изображения... Ну вы поняли. Ничего хорошего не будет.
Как с этим бороться? С одной стороны достаточно легко, с другой, мы получив проблему на одном уровне, просто транслируем ее на уровень выше (в данном случае это нам ничем не грозит, а вот в других случаях, возможно описанное решение придется применять и дальше).
Теперь придется править не только код метода отвечающего за скачивание, но и метод его вызывающий:
private async void
Button_Click(object sender, RoutedEventArgs e)
{WriteLog("Начало скачивания");
await DownloadImageAsync();
WriteLog("Окончание скачивания");
}
private async Task DownloadImageAsync()
{byte[] buffer = await (new WebClient()).DownloadDataTaskAsync(new Uri(tbURI.Text));
WriteLog("Изображение считано");
BitmapImage source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(buffer);
source.EndInit();
imView.Source = source;
WriteLog("Изображение отображено");
}
Как видно, изменений три:
1. Изменен тип возвращаемого значения у скачивающего картинку метода с void на Task.
2. К имени этого метода добавлен суффикс Async (рекомендуют так делать, чтобы эти методы были легко узнаваемыми).
3. Обработчик клика на кнопке сам стал async.
Все, теперь все работает как и ожидалось:
На этом можно было бы и закончить рассказ, если бы не одно но, про которое должен был возникнуть вопрос при взгляде на метод DownloadImageAsync. У него изменился тип возвращаемого значения с void на Task, а вот строчки return, у него как не было, так и нет. Это еще один "синтаксический сахар", который появился в c# 5. Ну а теперь, действительно все.
Комментариев нет:
Отправить комментарий