Запрос был прерван: не удалось создать безопасный канал SSL/TLS в приложении Windows 8 Metro

У меня есть список 350 загружаемых URL-адресов изображений. Я загружаю 10 изображений параллельно одним выстрелом, запуская несколько задач. Но после загрузки N количества изображений внезапно мой код выдает следующее исключение.

Исключение: "Произошла ошибка при отправке запроса".

InnerException: "Запрос был прерван: не удалось создать SSL/TLS безопасный канал".

StackTrace: "at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task задача)\r\n в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task задача)\r\n в System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n...

Я создал образец проекта, чтобы воспроизвести это исключение. В моей руке у меня 2 теста. Вы можете загрузить текущий тестовый проект из My Sky Drive Here. Щелкните правой кнопкой мыши файл HTTPClientTestCases1and2.zip и загрузите.

Случай 1: использование одного экземпляра HttpClient для загрузки всего изображения.

В этом случае я отправляю параллельный запрос на 10 URL-адресов, используя тот же HttpClient. В этом случае загрузка выполняется в большинстве случаев. После последней успешной загрузки изображения подождите минимум 40 секунд (максимум 1 минута 40 секунд), чтобы отправить следующий запрос параллельной загрузки для следующей партии. Из-за этого исключается одно изображение. Но так много мест написано и предложило использовать один HttpClient для множественного запроса.

   public async void DownloadUsingSingleSharedHttpClient(Int32 imageIndex)
    {   
        Uri url = new Uri(ImageURLs[imageIndex]);

        UnderDownloadCount++;

        try
        {
            Byte[] contentBytes = null;

            try
            {
                // Exception IS THROWN AT LINE BELOW 
                HttpResponseMessage response = await _httpClient.GetAsync(url);

                contentBytes = await response.Content.ReadAsByteArrayAsync();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Download Failed at GetAsync() :" + ex.Message);
                throw ex;
            }

            DownloadedCount++;

            if (OnSuccess != null)
                OnSuccess(this, new DownloadSuccessEventArgs() { Index = imageIndex, Data = contentBytes });
        }
        catch (HttpRequestException hre)
        {
            DownloadFailedCount++;
            if (OnFailed != null)
                OnFailed(hre, null);
        }
        catch (TaskCanceledException hre)
        {   
            DownloadFailedCount++;
            if (OnFailed != null)
                OnFailed(hre, null);
        }
        catch (Exception e)
        {
            DownloadFailedCount++;
            if (OnFailed != null)
                OnFailed(e, null);
        }
    }

Случай 2: Создание нового экземпляра HttpClient для каждого изображения Загрузка

В этом случае он просто терпит неудачу очень часто из-за того же исключения при одновременном загрузке изображений.

public async void DownloadUsingCreatingHttpClientEveryTime(Int32 imageIndex)
{
    Uri url = new Uri(ImageURLs[imageIndex]);

    UnderDownloadCount++;
    try
    {
        Byte[] contentBytes = null;

        using (HttpClientHandler _handler = new HttpClientHandler())
        {
            _handler.AllowAutoRedirect = true;
            _handler.MaxAutomaticRedirections = 4;

            using (HttpClient httpClient = new HttpClient(_handler))
            {
                httpClient.DefaultRequestHeaders.ExpectContinue = false;
                httpClient.DefaultRequestHeaders.Add("Keep-Alive", "false");

                try
                {
                    // Exception IS THROWN AT LINE BELOW 
                    contentBytes = await httpClient.GetByteArrayAsync(url.OriginalString);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine("Download Failed :" + ex.Message);
                    throw ex;
                    }
                }

            _handler.Dispose();
        }

        DownloadedCount++;

        if (OnSuccess != null)
            OnSuccess(this, new DownloadSuccessEventArgs() { Index = imageIndex, Data = contentBytes });
    }
    catch (HttpRequestException hre)
    {
        DownloadFailedCount++;
        if (OnFailed != null)
            OnFailed(hre, null);
    }
    catch (TaskCanceledException hre)
    {
        DownloadFailedCount++;
        if (OnFailed != null)
            OnFailed(hre, null);
    }
    catch (Exception e)
    {
        DownloadFailedCount++;
        if (OnFailed != null)
            OnFailed(e, null);
    }
}

Пожалуйста, отредактируйте следующую функцию в MainPage.xaml.cs, чтобы проверить два случая

 private void Send10DownloadRequestParallel()
    {
        for (Int32 index = 0; index < 10; index++)
        {
            Task.Run(() =>
            {   
                Int32 index1 = rand.Next(0, myImageDownloader.ImageURLs.Count - 1);

                UpdateDownloadProgress();

                // Case 1: Download Using Single Shared HttpClient
                // myImageDownloader.DownloadUsingSingleSharedHttpClient(index1);

                // OR

                // Case 2: Download Using Creating Http Client Every Time
                myImageDownloader.DownloadUsingCreatingHttpClientEveryTime(index1);
            });
        }
    }

Мой вопрос: Что я делаю неправильно? Каков наилучший способ реализации параллельного загрузчика в WinRT, преодолев это исключение.

Ответ 1

Я запустил ваше примерное приложение и получаю ошибки только в нескольких сценариях:

  • Когда изображение, запрашиваемое вашим приложением, не существует, клиент .NET HTTP выдает исключение. Ваш обработчик не совсем справляется с этим случаем, так как внутреннее исключение равно NULL. Мне пришлось немного изменить этот код:

    async void myImageDownloader_OnFailed(object sender, EventArgs e)
    {
        await App.CurrentDispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate
        {   
            TimeSpan time =(DateTime.Now -dateTimeSuccess);
    
            String timeGap = "Ideal For:" + time.ToString() + "\n";
            ErrorListBox.Text += "\n Failed When: " + DownloadInfo.Text + "\n";
            ErrorListBox.Text += timeGap;
    
            // CX - added null check for InnerException, as these are NULL on HTTP result status 404
            var ex = sender as Exception;
            if (ex.InnerException != null)
                ErrorListBox.Text += ex.InnerException.Message;
            else
                ErrorListBox.Text += "Inner Exception null - Outer = (" + ex.ToString() + ")";
        });
    }
    
  • Единственный раз, когда я получил вашу другую ошибку Could not create SSL/TLS secure channel in Windows 8 Metro App, - это когда я использовал прокси-сервер отладки HTTP (Fiddler). Если я не пользователь Fiddler, который перехватывает все вызовы HTTP (S), то у меня нет проблем с загрузкой. Я даже начал несколько загрузок в быстрой последовательности (щелкнув синюю область загрузки несколько раз в течение одной секунды). В результате было загружено все предметы (за исключением 404 ошибок, как указано выше).

Вот скриншот успешных загрузок (опять же за исключением 404). На этом скриншоте работает тестовый пример # 2 (несколько экземпляров HttpClient). Я выполнил тестовый пример № 1 (единственный экземпляр HttpClient), и результаты также были успешными.

Test application screenshot

Короче говоря, я не видел проблем, с которыми вы сталкиваетесь. Единственное, что я могу придумать, это попробовать попробовать свое приложение с другой машины или местоположения.