среда, 12 сентября 2012 г.

Синтаксический сахар async и await

Вчера, в разговоре со Стасом, мы что-то начали обсуждать async и await с точки зрения, понимания. Вот пришел новый программист, вот показали ему магию, а как оно работает? А кто же его знает? В этом посте, попробую предложить вариант реализации того же функционала на старых, добрых потоках.

Итак, для примеров воспользуемся классом WebClient, у которого появились перегрузки методов поддерживающие async.
Для демонстрации, я воспользуюсь простеньким Windows приложением, с четырьмя кнопками. Задача будет достаточно простой, необходимо скачать файл из интернета, а т.к. файл достаточно большой, необходимо на время скачивания добиться отсутсвия зависания приложения.
Вполне логично, что решение в лоб, будет работать, но интерфейс повиснет:

private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    WebClient client = new WebClient();
    client.DownloadFile("http://img-fotki.yandex.ru/get/6503/307344.0/0_663fb_eb16b32e_orig", @"d:\1.jpg");
    button1.Enabled = true;
}
Ок, воспользуемся перегруженным методом, для закачки в отдельном потоке:

private async void button2_Click(object sender, EventArgs e)
{
    button2.Enabled = false;
    WebClient client = new WebClient();
    await client.DownloadFileTaskAsync(new Uri("http://img-fotki.yandex.ru/get/6503/307344.0/0_663fb_eb16b32e_orig"), @"d:\2.jpg");
    button2.Enabled = true;
}
Изменения незначительные, собственно, кроме изменения номер кнопки появился только async в объявлении метода, да поменялась строка отвечающая за загрузку. Кстати, не залезая в MSDN кто сходу скажет, чем отличается использованный в примере DownloadFileTaskAsync, от еще одной перегрузки: DownloadFileAsync? (для тех кто не знает и ленится посмотреть в MSDN, напишу в конце статьи).
Но ладно, мы сейчас не об этом. Что же здесь за магия? В первом приближении, если вы уже работали с таким классом как Task, можно воспользоваться грязным хаком и уйти от async/await.
Например, мы можем написать вот такой код:
private void button3_Click(object sender, EventArgs e)
{
    button3.Enabled = false;
    WebClient client = new WebClient();
    client
        .DownloadFileTaskAsync(new Uri("http://img-fotki.yandex.ru/get/6503/307344.0/0_663fb_eb16b32e_orig"), @"d:\3.jpg")
        .ContinueWith(t => button3.Enabled = true);
}
Кто давно читает мой блог или уже работал с многопоточным доступом к визуальным компонентам, сразу скажет, что не заработает. Т.к. изменение состояния кнопки идет из потока отличного от потока в котором кнопка создавалась. Но это лирика, можно поправить:

private void button3_Click(object sender, EventArgs e)
{
    button3.Enabled = false;
    WebClient client = new WebClient();
    client
        .DownloadFileTaskAsync(new Uri("http://img-fotki.yandex.ru/get/6503/307344.0/0_663fb_eb16b32e_orig"), @"d:\3.jpg")
        .ContinueWith(t => this.Invoke((Action)(() => button3.Enabled = true)));
}
Я ошибаюсь, или магии стало еще больше? Давайте попробуем это все переписать на потоках, как я и обещал в начале статьи:

private void button4_Click(object sender, EventArgs e)
{
    button4.Enabled = false;
    Thread worker = new Thread(() =>
        {
            WebClient client = new WebClient();
            client.DownloadFile("http://img-fotki.yandex.ru/get/6503/307344.0/0_663fb_eb16b32e_orig", @"d:\4.jpg");
            this.Invoke((Action)(() => button4.Enabled = true));
        });
    worker.Start();
}
Не знаю, но мне кажется, что получилось даже читаемей чем вторая версия.
Ну и вместо вывода. В 2012 году от рождества христова надо пользоваться первым вариантом, но знать как и почему работает третий и второй. Причем именно в таком порядке: третий и второй.
P.s. Тот метод что без Task запускает асинхронное скачивание и сразу возвращает управление. Т.е. файл еще не скачается, а кнопка уже разблокируется.

Комментариев нет:

Отправить комментарий