вторник, 27 марта 2012 г.

Async и await

Иногда ожидание бывает дольше, чем хотелось бы пользователю. И если во время этого ожидания приложение еще и не реагирует на действия пользователя, то он вообще в гневе. Достаточно давно, я уже писал как можно реализовать многопоточное приложение. В Framework 4.5 появилась пара ключевых слов, которые позволяют реализовать еще один механизм выолнения длительной операции в отдельном потоке. Это слова async и await.

В качестве примера возьмем загрузку ежедневных курсов с сайта Центробанка России. У них если кто не знает, по адресу http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx есть соответствующий сервис.
Итак создаем WPF приложение. Правым кликом на проекте в Solution Explorer-е и выбираем добавить ссылку на сервис (Add Service Reference):
В главную форму нашего приложения кидаем DataGrid и подписываемся на обработчик окончания загрузки окна:

<Window x:Class="CourseOnDay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded_1">
    <Grid>
        <DataGrid x:Name="dgMain" />
    </Grid>
</ Window>

Обработчик будет выглядеть вот так:

private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
    CBR.DailyInfoSoapClient client = new CBR.DailyInfoSoapClient();
    DataTable result = client.GetCursOnDate(DateTime.Now).Tables[0];
    dgMain.ItemsSource = result.Rows.Cast<DataRow>().Select(
    r => new
    {
        Валюта = r[0],
        Количество = r[1],
        Стоимость = r[2]
    }
    );
}

Ну и результат:
Единственный недостаток, это то, что приложение "подвисает" на время загрузки данных. Давайте применим async и await для решения данной задачи. Что придется изменить? Совсем немного:

private async void Window_Loaded_1(object sender, RoutedEventArgs e)
{
    CBR.DailyInfoSoapClient client = new CBR.DailyInfoSoapClient();
    DataSet result = await client.GetCursOnDateAsync(DateTime.Now);
    dgMain.ItemsSource = result.Tables[0].Rows.Cast<DataRow>().Select(
    r => new
    {
        Валюта = r[0],
        Количество = r[1],
        Стоимость = r[2]
    }
    );
}

Как видно, изменений всего ничего:
1. Метод помечен async.
2. Вызывается не метод GetCursOnDate, а  GetCursOnDateAsync. Данный метод возвращает  Task.
3. Чтобы дождаться результата выполнения запущенного в отдельном потоке и Task преобразовать к DataSet нам и нужно второе ключевое слово await.

Собственно все.

4 комментария:

  1. Спасибо познавательно, не подскажешь как организовать progress bar пока выполняется GetCursOnDateAsync(DateTime.Now)??

    ОтветитьУдалить
    Ответы
    1. К сожалению, в данном случае показать ProgressBar не получится, т.к. метод не предоставляет информацию о прогрессе как таковом. Есть факт запуска и факт окончания загрузки. Мы в таких случаях показываем не ProgressBar, а "мотылятор". "Мотылятор" это компонент который показывает некую анимацию во время загрузки. Например кружочки бегут по кругу. Т.е. мы пользователю говорим, что работа идет, но, к сожалению, без индикатора успешности процесса.

      Удалить
  2. GetCursOnDateAsync(DateTime.Now).Status а это тогда что?

    ОтветитьУдалить
    Ответы
    1. Дело в том, что Async методы возвращают, на самом деле экземпляр класса Task. Если еще не читали, то прочитайте вот это http://losev-al.blogspot.ru/2012/10/blog-post_16.html У класса Task есть свойство Status, в котором храниться перечисление со значениями: выполняется, остановлен, ошибка, завершен и т.д. Вы сможете на основе этих значений вывести сколько процентов скачалось? Я нет...

      Удалить