Как загрузить файл в строку с обратным вызовом? - PullRequest
6 голосов
/ 26 апреля 2010

Я хотел бы использовать WebClient (или есть другой лучший вариант?), Но есть проблема. Я понимаю, что открытие потока занимает некоторое время, и этого нельзя избежать. Однако чтение занимает, как ни странно, гораздо больше времени, чем полное чтение.

Есть ли лучший способ сделать это? Я имею в виду два способа, чтобы строки и файл. Progress - мой собственный делегат, и он работает хорошо.


ПЯТОЕ ОБНОВЛЕНИЕ:

Наконец-то мне удалось это сделать. Тем временем я проверил некоторые решения, которые заставили меня понять, что проблема лежит в другом месте.

Я протестировал пользовательские WebResponse и WebRequest объекты, библиотеку libCURL.NET и даже Sockets.

Разница во времени заключалась в сжатии gzip. Длина сжатого потока была просто вдвое меньше обычной длины потока, поэтому время загрузки с браузером составляло менее 3 секунд.

Я добавлю код, если кто-то захочет узнать, как я это решил: (некоторые заголовки не нужны)

public static string DownloadString(string URL)
    {
        WebClient client = new WebClient();
        client.Headers["User-Agent"] = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1045 Safari/532.5";
        client.Headers["Accept"] = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
        client.Headers["Accept-Encoding"] = "gzip,deflate,sdch";
        client.Headers["Accept-Charset"] = "ISO-8859-2,utf-8;q=0.7,*;q=0.3";

        Stream inputStream = client.OpenRead(new Uri(URL));
        MemoryStream memoryStream = new MemoryStream();
        const int size = 32 * 4096;
        byte[] buffer = new byte[size];

        if (client.ResponseHeaders["Content-Encoding"] == "gzip")
        {
            inputStream = new GZipStream(inputStream, CompressionMode.Decompress);
        }

        int count = 0;
        do
        {
            count = inputStream.Read(buffer, 0, size);
            if (count > 0)
            {
                memoryStream.Write(buffer, 0, count);
            }
        }
        while (count > 0); 

        string result = Encoding.Default.GetString(memoryStream.ToArray());
        memoryStream.Close();
        inputStream.Close();
        return result;
    }

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

Ответы [ 2 ]

1 голос
/ 26 апреля 2010

Вы получаете только последние iSize байтов из вашего файла, так как вы перезаписываете свой буфер на каждой итерации, вы нигде не сохраняете буфер. Вот пример того, как сохранить файл в памяти, используя MemoryStream.

var totalBytes = new MemoryStream(1024 * 1024);
while ((iByteSize = streamRemote.Read(byteBuffer, 0, iByteSize)) > 0)
{
    totalBytes.Write(byteBuffer, 0, iByteSize);
    iRunningByteTotal += iByteSize;

    //Some progress calculation
    if (Progress != null) Progress(iProgressPercentage);
}

Когда вся загрузка будет завершена, вы можете преобразовать ее в текст.

var byteArray = totalBytes.GetBuffer();
var numberOfBytes = totalBytes.Length;
var text = Encoding.Default.GetString(byteArray, 0, numberOfBytes);

Обновление: метод DownloadStringAsync в основном делает то же самое, что и выше, но не даст вам никакого индикатора прогресса. Однако есть несколько других асинхронных методов, которые вызовут событие DownloadProgressChanged.

Обновление 2: Относительно времени отклика. Вы рассчитали время загрузки ресурса с помощью какого-либо другого инструмента? Основные браузеры имеют встроенную поддержку для синхронизации таких событий.

Далее, это статический файл, который вы обслуживаете, или контент создается на стороне сервера?

Третье, что приходит на ум, это буферизация на стороне сервера. Например. если используется свойство Response.Buffer в ASP.Net, клиенту ничего не будет отправлено, пока весь файл / страница не будет выполнен на стороне сервера. Таким образом, клиенту придется ждать, прежде чем он сможет начать загрузку.

1 голос
/ 26 апреля 2010

Меня очень смущает двойное чтение, но оно выглядит , как будто вы действительно собираетесь сделать что-то вроде:

        StringBuilder sb = new StringBuilder();           
        using (StreamReader reader = new StreamReader(streamRemote))
        {
            char[] charBuffer = new char[bufferSize];
            int charsRead;
            while ((charsRead = reader.Read(charBuffer, 0, bufferSize)) > 0)
            {
                sb.Append(charBuffer, 0, charsRead);
                //Some progress calculation

                if (Progress != null) Progress(iProgressPercentage);
            }
        }
        string result = sb.ToString();

Посмотрите, работает ли это так, как нужно. Однако мне интересно, не является ли Progress причиной сброса; попробуйте это без этого назначенного, посмотрите, делает ли это это быстрее. Или запускайте это периодически:

            //[snip]
            int iteration = 0, charsRead;
            while ((charsRead = reader.Read(charBuffer, 0, bufferSize)) > 0)
            {
                sb.Append(charBuffer, 0, charsRead);
                //Some progress calculation
                if((++iteration % 20) == 0 && Progress != null) {
                    Progress(iProgressPercentage);
                }
            }
            //[snip]

Также попробуйте увеличить размер буфера.

...