Как асинхронно анализировать XML из HttpWebRequest?
Моя основная программа выполняет 8 задач, используя Task.Factory.StartNew
Каждая задача будет запрашивать результат в формате XML у веб-службы, а затем анализировать ее в коллекции, которую можно записать в MSSQL с помощью TVP.
Программа работает, но повышение эффективности с использованием TPL не то, что я ожидал. После использования секундомера в разных точках мне кажется, что задачи мешают друг другу, может быть одна блокирует другую. Все цифры указывают на раздел загрузки, который использует HttpWebRequest.
После поиска и прочтения немного об асинхронном программировании в C# я попытался изменить свой код, чтобы асинхронно запускать секцию загрузки, но результат все еще показывает аналогичный уровень блокировки без использования асинхронного кодирования.
Я нашел 3 типа кодирования и несколько ссылок на них:
Как использовать HttpWebRequest (.NET) асинхронно?-При использовании этого метода я передаю XDocument, используя пользовательский объект в методе раздела загрузки
Асинхронное программирование в C# с использованием итераторов http://tomasp.net/blog/csharp-async.aspx-string / stream возвращается и анализируется с использованием XDocument.Load/Parse в основном методе.
Ниже блок кода показывает последний метод, найденный и реализованный в моем коде
Основной класс, который запускает задачи
private static void test() {
DBReader dbReader = new DBReader();
Dictionary<string, DateTime> jobs = dbReader.getJob();
JobHandler jh = new JobHandler();
Stopwatch swCharge = new Stopwatch();
Stopwatch swDetail = new Stopwatch();
Stopwatch swHeader = new Stopwatch();
//more stopwatch
Task[] tasks = new Task[] {
Task.Factory.StartNew(() => jh.processData<RawChargeCollection, RawCharge>(jobs["RawCharge"], 15, swCharge)),
Task.Factory.StartNew(() => jh.processData<RawDetailCollection, RawDetail>(jobs["RawDetail"], 15, swDetail)),
Task.Factory.StartNew(() => jh.processData<RawHeaderCollection, RawHeader>(jobs["RawHeader"], 15, swHeader))
};
Task.WaitAll(tasks);
}
Метод processData
public void processData<T, S>(DateTime x, int mins, Stopwatch sw)
where T : List<S>, new()
where S : new() {
DateTime start = x;
DateTime end = x.AddMinutes(mins);
string fromDate, toDate;
StringBuilder str = new StringBuilder();
XMLParser xmlParser = new XMLParser();
DBWriter dbWriter = new DBWriter();
while (end <= DateTime.UtcNow) {
fromDate = String.Format("{0:yyyy'-'MM'-'dd HH':'mm':'ss}", start);
toDate = String.Format("{0:yyyy'-'MM'-'dd HH':'mm':'ss}", end);
try {
sw.Restart();
WebserviceClient ws = new WebserviceClient();
XDocument xDoc = null;
var task = ws.GetRawData<S>(fromDate, toDate);
xDoc = XDocument.Parse(task.Result);
//show the download time
sw.Restart();
T rawData = xmlParser.ParseXML<T, S>(xDoc);
if (rawData.Count != 0) {
sw.Restart();
dbWriter.writeRawData<T, S>(rawData, start, end);
//log success
}
else {
//log no data
}
}
catch (Exception e) {
//log fail
}
finally {
start = start.AddMinutes(mins);
end = end.AddMinutes(mins);
}
}
}
GetRawData просто отвечает за создание необходимого URL, используемого в GetData.
Скачать раздел данных:
private static Task<string> GetData(string param) {
string url = String.Format("my working URL/{0}", param);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.MediaType = "application/xml";
Task<WebResponse> task = Task.Factory.FromAsync(
request.BeginGetResponse,
asyncResult => request.EndGetResponse(asyncResult),
(object)null);
return task.ContinueWith(t => ReadStreamFromResponse(t.Result));
}
private static string ReadStreamFromResponse(WebResponse response) {
using (Stream responseStream = response.GetResponseStream())
using (StreamReader sr = new StreamReader(responseStream)) {
//Need to return this response
string strContent = sr.ReadToEnd();
return strContent;
}
}
В методе processData я рассчитал код, необходимый для загрузки с веб-сервиса. Загрузка занимает от 400 мс до 100000 мс. Нормальное время от 3000 до 8000 мс. Если я просто запускаю 1 задачу, время процесса клиента будет лишь немного больше времени процесса сервера.
Однако после выполнения большего количества задач загрузка, которая занимает от 450 мс до 3000 мс (или что-либо еще) на сервере, может теперь занять до 8000 мс-90000 мс для клиента, чтобы завершить раздел загрузки.
В моем сценарии узкое место должно быть на стороне сервера, из моего журнала видно, что клиент есть.
В большинстве статей, посвященных асинхронному программированию, C# кажется демонстрационным чтением и обработкой потока / строки без примера для XML. Мой код не работает из-за XML?? Если нет, то в чем проблема моего кода?
РЕДАКТИРОВАТЬ: Да, моя машина разработчика и пользователей / целевой машины является XP, слишком много, чтобы использовать.net 4.5 или CTP.
ServicePointManager.DefaultConnectionLimit и app.config connectionManagement, кажется, одно и то же, поэтому я выбираю app.config, поскольку его можно изменить.
Сначала изменение максимального соединения очень помогло, но не решило проблему. После синхронизации блока кода с Thread.Sleep(random) кажется, что "блокирование" не относится к параллельному коду.
Сначала загрузите processData из веб-службы (здесь необходимо максимальное подключение), затем выполните небольшое сопоставление, наконец, запись в БД, запись в БД никогда не занимает более 1 секунды, по сравнению с загрузкой ничего не происходило, но после добавления максимального подключения к БД (то же самое) номер как веб-сервис) не было никакого ожидания внезапно.
Так что максимальное подключение к БД также имеет значение. Но я не понимаю, почему запись в БД с 150-600 мс может вызвать ожидание более 20 секунд.
Что даже смущает меня, так это время ожидания в блоке загрузки, а не в блоке записи БД.
1 ответ
Я бы вернулся к более простой форме, по крайней мере, для отладки, где каждый из них представлял собой "нормальный" / синхронный код. Так как в худшем случае вы излишне заблокируете 8 потоков, я бы пока не подумал, что это важно.
Я думаю, что вместо этого вы по умолчанию ограничиваете количество одновременных запросов.
От этой связанной темы так...
Максимальное количество одновременных запросов HttpWebRequests
... вы можете посмотреть на то, на что указал Джон Скит, элемент connectionManagement:
http://msdn.microsoft.com/en-us/library/fb6y0fyc.aspx
Элемент connectionManagement определяет максимальное количество подключений к серверу или группе серверов.
Кроме того, рекомендация Джона заменить http-вызовы просто Thread.Sleep, чтобы увидеть, влияет ли параллелизм, превосходна. Если все ваши 8 задач могут выполнять параллельные вызовы Thread.Sleep, то ваша проблема не в параллельности "верхнего уровня", а в ограничении, накладываемом тем, что они делают (например, ограничение одновременного соединения по умолчанию).