#c# #wpf #asynchronous #task
#c# #wpf #асинхронный #задача
Вопрос:
Я еще раз повторно просматривал асинхронные задачи. Независимо от того, как я устанавливаю задачи, мое приложение постоянно страдает от зависания пользовательского интерфейса. У меня есть следующий код для загрузки строки с веб-страницы:
internal string DownloadString(string URL)
{
var result = LoadCompanyContracts(URL);
return result.Resu<
}
internal async Task<string> LoadCompanyContracts(string URL)
{
Task<string> task2 = Task<string>.Factory.StartNew(() =>
{
for (int i = 0; i <= 10000000; i ) Console.WriteLine(i);
WebClient wc = new WebClient();
string tmp = wc.DownloadString(new Uri(URL));
return tmp;
});
return task2.Resu<
}
Когда я выполняю эту задачу и во время цикла for, пользовательский интерфейс моего приложения зависает. Хотя я считаю, что этот код не должен замораживать пользовательский интерфейс, я не могу найти решение. Я перепробовал много разных вариантов и действительно хочу использовать задачи вместо потоков или событий с webclient async.
Информация: Я использую .net 4.5 для своего проекта. Разница в моем коде в том, что эти функции находятся внутри библиотеки классов (не знаю, имеет ли это значение).
Возможно ли запустить этот код без блокировки пользовательского интерфейса с помощью async await, вызвав функцию DownloadString из моего кода? Если нет, то каковы альтернативы (любые хорошие пакеты nuget)?
Комментарии:
1.
result.Result
это блокирует2. Это неправильный способ использования
async
и Task.Result.3. Интересно, что сам компилятор выдаст вам предупреждение, в котором конкретно говорится, что
LoadCompanyContracts
будет выполняться синхронно.
Ответ №1:
async
Ключевое слово не заставляет что-либо выполняться асинхронно, оно позволяет вам использовать await
для ожидания уже асинхронной операции. Вам нужно использовать DownloadStringTaskAsync для действительно асинхронной загрузки:
internal async Task<string> LoadCompanyContracts(string URL)
{
....
using(var wc = new WebClient())
{
string tmp = await wc.DownloadStringTaskAsync(new Uri(URL));
return tmp;
}
}
await
само по себе возвращает выполнение в исходный контекст выполнения (т. Е. поток пользовательского интерфейса). Это может быть или не быть желательным, вот почему библиотечный код обычно использует ConfigureAwait(false);
и позволяет конечному пользователю библиотеки решать, как ожидать:
string tmp = await wc.DownloadStringTaskAsync(new Uri(URL))
.ConfigureAwait(false);
Наконец, нет смысла ожидать, если вы собираетесь вызывать .Result
из функции верхнего уровня. Вообще нет смысла использовать await
, если вы не хотите использовать результат метода в своем коде. LoadCompanyContracts
может быть просто:
internal Task<string> LoadCompanyContracts(string URL)
{
....
using(var wc = new WebClient())
{
return wc.DownloadStringTaskAsync(new Uri(URL))
.ConfigureAwait(false);
}
}
Упс
Как правило, вам вообще не нужно использовать await, если вы просто возвращаете результат асинхронной операции. Метод мог бы просто, return wc.DownloadStringTaskAsync(..);
НО это заставило бы метод вернуть и удалить WebClient
перед завершением загрузки. Избегание using
блокировки также не является решением, поскольку это позволит дорогостоящему объекту, такому как WebClient
, существовать дольше, чем необходимо.
Вот почему HttpClient предпочтительнее WebClient
: один экземпляр поддерживает несколько одновременных вызовов, что означает, что вы можете иметь только один экземпляр, например, в качестве поля, и повторно использовать его, например:
HttpClient _myClient =new HttpClient();
internal Task<string> LoadCompanyContractsAsync(string URL)
{
....
return _myClient.GetStringAsync(new Uri(URL))
.ConfigureAwait(false);
}
}
Вы могли бы избавиться от своего, DownloadString
поскольку он ничего не делает поверх LoadCompanyContracts
. Если он использует результат LoadCompanyContracts
, его следует переписать как:
internal async Task<string> DownloadString(string URL)
{
var result = await LoadCompanyContracts(URL);
//Do something with the result
return resu<
}
Редактировать
В исходном ответе использовался DownloadStringAsync, который является устаревшим методом, который вызывает событие при завершении загрузки. Правильный метод — DownloadStringTaskAsync
РЕДАКТИРОВАТЬ 2 Поскольку мы говорим об пользовательском интерфейсе, код можно сделать асинхронным вплоть до верхнего обработчика событий, используя async void
синтаксис для обработчика, eg async void Button1_Click
, eg:
async void LoadCustomers_Click(...)
{
var contracts=await LoaCompanyContracts(_companyUrls);
txtContracts>Text=contracts;
}
В этом случае мы хотим вернуться к исходному потоку, поэтому мы не используем ConfigureAwait(false);
Комментарии:
1.@Evangelink Это просто привело бы к потере потока.
DownloadStringAsync
выполняется в фоновом потоке или, скорее, использует порты завершения ввода-вывода, поэтому вообще не тратит поток впустую в ожидании результата.Task.Run
с другой стороны, поток тратится впустую в ожидании ответа.2. @PanagiotisKanellopoulos упс, это должна быть строка DownloadStringTaskAsync . Исправлено
3. @PanagiotisKanellopoulos кто-то пропустил собрания DotNetZone на TPL за последние 3 года ….
4. @PanagiotisKanellopoulos вы должны использовать async вплоть до вашего уровня пользовательского интерфейса — не используйте
.Result
нигде. В противном случае ваш код зайдет в тупик.5. @PanagiotisKanellopoulos почему?
var result = await AsyncMethod()
выдает результат, какresult
для остальной части контекста этого метода.await Task
является асинхронным эквивалентомTask.Result
. Дело в том, что вызывающий объект также должен быть асинхронным (и его вызывающий объект, и так далее), Что является основным фундаментальным принципом асинхронности — если вы блокируете асинхронный процесс, он перестает быть асинхронным.