2014-01-08 5 views
2

У меня есть куча медленных функций, которые являются по существу это:Как создать HttpWebRequest без прерывания async/wait?

private async Task<List<string>> DownloadSomething() 
{ 
    var request = 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 
    } 

} 

Это прекрасно работает и в асинхронном режиме для вызова WebRequest.Create исключения - это одна строки замерзает нить UI в течение нескольких секунд, что-то развалины цели асинхронная/Await.

У меня уже есть этот код, написанный с использованием BackgroundWorker s, который отлично работает и никогда не зависает от пользовательского интерфейса.
Все еще, Каков правильный, идиоматический способ создания веб-запроса в отношении async/await? Или, может быть, есть другой класс, который следует использовать?

Я видел this nice answer об асинхронном WebRequest, но даже там сам объект создается синхронно.

ответ

6

Интересно, что я не вижу блокирующую задержку с 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.

+0

Работает так. Тем не менее, я попытался адаптировать это «Task.Run» всего, а не только один бит внутри функции (так что есть только один дополнительный поток, который делает свой собственный бизнес 'ждут', а кишки каждой функции не заботятся о том, в какой поток они находятся). Это не сработало, потому что я возвращаю прогресс через «IProgress», где я обращаюсь к элементам пользовательского интерфейса, и это не удается с помощью сквозного доступа. Возможно ли каким-либо образом заставить внешнюю лямбду передать «Task.Run()» использовать правильный контекст синхронизации или мне придется поместить 'Invoke' в мой метод «Report»? – GSerg

+0

@GSerg, я опубликовал обновленный. Это то, что тебе нужно? – Noseratio

+0

Это, по сути, то, что я получил после принятия вашего первоначального ответа. Увы, прохождение одного и того же токена отмены таким образом не вызвало магических обращений к потоку пользовательского интерфейса. Все еще не выполняется внутри 'progressIndicator.Report()' из-за сквозного доступа к пользовательскому интерфейсу. Я исправил его, проверив 'InvokeRequired' внутри функции отчетности и' Invoke'ing по мере необходимости, но это кажется позором (по сравнению с фоновым работником, где это происходит автоматически) и нечеткой абстракцией. Я могу оставить его как есть, но мне очень хотелось бы узнать, как заставить его работать, не нарушая абстракции. – GSerg

0

Я думаю, вам нужно использовать HttpClient.GetAsync(), который возвращает задачу из HTTP-запроса.

http://msdn.microsoft.com/en-us/library/hh158912(v=vs.110).aspx

Это может зависеть немного о том, что вы хотите, чтобы вернуться, но HttpClient имеет целую кучу асинхронных методов запросов.

+1

К сожалению, это дает то же самое, что и на линии 'waitait PostAsync'. Вешание останавливается до завершения ожидания, поэтому я предполагаю, что внутри «HttpClient» создается «HttpWebRequest», которое синхронно создается и асинхронно считывается, что я и делаю вручную. – GSerg

+0

А я вижу, хороший момент. Я думаю, если вы зависите от результата запроса, вам всегда нужно подождать, пока он не будет закончен. Вы можете продолжать обрабатывать другие вещи, но вы не сможете взаимодействовать с результатом. В качестве бокового пункта есть действительно хорошая функция InCompletionOrder(), если вам нужно сделать несколько запросов и обработать данные в том порядке, в котором они возвращаются. https://gist.github.com/mswietlicki/6112628 – dougajmcdonald

Смежные вопросы