Интересно, что я не вижу блокирующую задержку с WebRequest.Create
или HttpClient.PostAsync
. Это может быть связано с настройкой DNS или конфигурацией прокси-сервера, хотя я бы ожидал, что эти операции будут реализованы внутренне как асинхронные.
Во всяком случае, как обходного вы можете запустить запрос на пул потоков, хотя это не то, что я обычно делают:
private async Task<List<string>> DownloadSomething()
{
var request = await Task.Run(() => {
// WebRequest.Create freezes??
return System.Net.WebRequest.Create("https://valid.url");
});
// ...
using (var ss = await request.GetRequestStreamAsync())
{
await ss.WriteAsync(...);
}
using (var rr = await request.GetResponseAsync())
using (var ss = rr.GetResponseStream())
{
//read stream and return data
}
}
Это будет держать UI отзывчивый, но это может быть трудно до cancel, если пользователь хочет остановить операцию. Это потому, что вам нужно иметь экземпляр WebRequest
, чтобы иметь возможность называть Abort
.
Использование HttpClient
, отмена будет возможно, что-то вроде этого:
private async Task<List<string>> DownloadSomething(CancellationToken token)
{
var httpClient = new HttpClient();
var response = await Task.Run(async() => {
return await httpClient.PostAsync("https://valid.url", token);
}, token);
// ...
}
С HttpClient
, вы можете также зарегистрировать httpClient.CancelPendingRequests()
обратный вызов на знак отмены, как this.
[UPDATE] На основании комментариев: в вашем оригинальном случае (перед введением
Task.Run
) вам, вероятно, не нужен шаблон
IProgress<I>
. До тех пор, пока
DownloadSomething()
был вызван в потоке пользовательского интерфейса, каждый шаг выполнения после каждого
await
внутри
DownloadSomething
будет возобновлен в одном и том же потоке пользовательского интерфейса, поэтому вы можете просто обновить интерфейс непосредственно между
awaits
.
Теперь, чтобы запустить целое DownloadSomething()
через Task.Run
в потоке бассейна, вам нужно будет передать экземпляр IProgress<I>
в него, например.:
private async Task<List<string>> DownloadSomething(
string url,
IProgress<int> progress,
CancellationToken token)
{
var request = System.Net.WebRequest.Create(url);
// ...
using (var ss = await request.GetRequestStreamAsync())
{
await ss.WriteAsync(...);
}
using (var rr = await request.GetResponseAsync())
using (var ss = rr.GetResponseStream())
{
// read stream and return data
progress.Report(...); // report progress
}
}
// ...
// Calling DownloadSomething from the UI thread via Task.Run:
var progressIndicator = new Progress<int>(ReportProgress);
var cts = new CancellationTokenSource(30000); // cancel in 30s (optional)
var url = "https://valid.url";
var result = await Task.Run(() =>
DownloadSomething(url, progressIndicator, cts.Token), cts.Token);
// the "result" type is deduced to "List<string>" by the compiler
Обратите внимание, потому что DownloadSomething
является async
метода самого по себе, он теперь работает как вложенная задача, которая Task.Run
прозрачно разворачивает для вас. Подробнее об этом: Task.Run vs Task.Factory.StartNew.
Также вы можете проверить: Enabling Progress and Cancellation in Async APIs.
Работает так. Тем не менее, я попытался адаптировать это «Task.Run» всего, а не только один бит внутри функции (так что есть только один дополнительный поток, который делает свой собственный бизнес 'ждут', а кишки каждой функции не заботятся о том, в какой поток они находятся). Это не сработало, потому что я возвращаю прогресс через «IProgress», где я обращаюсь к элементам пользовательского интерфейса, и это не удается с помощью сквозного доступа. Возможно ли каким-либо образом заставить внешнюю лямбду передать «Task.Run()» использовать правильный контекст синхронизации или мне придется поместить 'Invoke' в мой метод «Report»? – GSerg
@GSerg, я опубликовал обновленный. Это то, что тебе нужно? – Noseratio
Это, по сути, то, что я получил после принятия вашего первоначального ответа. Увы, прохождение одного и того же токена отмены таким образом не вызвало магических обращений к потоку пользовательского интерфейса. Все еще не выполняется внутри 'progressIndicator.Report()' из-за сквозного доступа к пользовательскому интерфейсу. Я исправил его, проверив 'InvokeRequired' внутри функции отчетности и' Invoke'ing по мере необходимости, но это кажется позором (по сравнению с фоновым работником, где это происходит автоматически) и нечеткой абстракцией. Я могу оставить его как есть, но мне очень хотелось бы узнать, как заставить его работать, не нарушая абстракции. – GSerg