Подтвердить что ты не робот

Проблемы с HttpClient и потоком

Год назад я написал командлет, который обрабатывает запросы Multipart/form-data, и для этого используется класс .net класса HttpClient. Я описал его подробно здесь.

В двух словах это ядро ​​моего командлета:

$networkCredential = New-Object -TypeName System.Net.NetworkCredential -ArgumentList @($Credential.UserName, $Credential.Password)
$httpClientHandler = New-Object -TypeName System.Net.Http.HttpClientHandler
$httpClientHandler.Credentials = $networkCredential

$httpClient = New-Object -TypeName System.Net.Http.Httpclient -ArgumentList @($httpClientHandler)

$packageFileStream = New-Object -TypeName System.IO.FileStream -ArgumentList @($packagePath, [System.IO.FileMode]::Open)

$contentDispositionHeaderValue = New-Object -TypeName  System.Net.Http.Headers.ContentDispositionHeaderValue -ArgumentList @("form-data")
$contentDispositionHeaderValue.Name = "fileData"
$contentDispositionHeaderValue.FileName = $fileName

$streamContent = New-Object -TypeName System.Net.Http.StreamContent -ArgumentList @($packageFileStream)
$streamContent.Headers.ContentDisposition = $contentDispositionHeaderValue
$streamContent.Headers.ContentType = New-Object -TypeName System.Net.Http.Headers.MediaTypeHeaderValue -ArgumentList @("application/octet-stream")

$content = New-Object -TypeName System.Net.Http.MultipartFormDataContent
$content.Add($streamContent)

try
{
    $response = $httpClient.PostAsync("$EndpointUrl/package/upload/$fileName", $content).GetAwaiter().GetResult()

    if (!$response.IsSuccessStatusCode)
    {
        $responseBody = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
        $errorMessage = "Status code {0}. Reason {1}. Server reported the following message: {2}." -f $response.StatusCode, $response.ReasonPhrase, $responseBody

        throw [System.Net.Http.HttpRequestException] $errorMessage
    }

    return [xml]$response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
}
catch [Exception]
{
    throw
}
finally
{
    if($null -ne $httpClient)
    {
        $httpClient.Dispose()
    }

    if($null -ne $response)
    {
        $response.Dispose()
    }
}

Я использовал этот код в задаче сборки VSTS более года с успехом. В последнее время это началось с перерывами. За один проход он преуспевает, а затем следующий терпит неудачу и так далее. Я не понимаю, почему это так, и я бы воспользовался некоторой помощью.

Код не работает при вызове метода PostAsync, и следующее исключение я вижу:

2017-03-24T15:17:38.4470248Z ##[debug]System.NotSupportedException: The stream does not support concurrent IO read or write operations.
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Net.ConnectStream.InternalWrite(Boolean async, Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Net.ConnectStream.BeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Net.Http.StreamToStreamCopy.TryStartWriteSync(Int32 bytesRead)
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Net.Http.StreamToStreamCopy.BufferReadCallback(IAsyncResult ar)
2017-03-24T15:17:38.4626512Z ##[debug]--- End of stack trace from previous location where exception was thrown ---
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
2017-03-24T15:17:38.4626512Z ##[debug]   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
2017-03-24T15:17:38.4626512Z ##[debug]   at CallSite.Target(Closure , CallSite , Object )
2017-03-24T15:17:38.4939015Z ##[error]The stream does not support concurrent IO read or write operations.

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

Есть ли у кого-нибудь предложение или тест, который я могу сделать, чтобы понять, что происходит и почему это не удается?

Буду признателен за любой совет.

ОБНОВЛЕНИЕ 1:

Я выполнил свой код, запустив его как фоновое задание с помощью командлета Start-Job, и я не могу заставить его сбой. Поэтому это должно быть связано с тем, как агент VSTS выполняет мой код. Я буду рыть. Если у вас есть какие-либо предложения, они по-прежнему приветствуются.

4b9b3361

Ответ 1

System.Net.Http.StreamContent и System.IO.FileStream создаются, но не удаляются.

Добавьте код для вызова Dispose, как в блоке finally.

try
{
    # enclose the program
}
finally
{
    if($null -ne $streamContent)
    {
        $streamContent.Dispose()
    }
    if($null -ne $packageFileStream)
    {
        $packageFileStream.Dispose()
    }
}

Ответ 2

Это исключение явно связано с потоками.

Вызов метода использует вызовы метода асинхронного вызова, но не правильно:

.ReadAsStringAsync().GetAwaiter().GetResult()

просто блокирует текущий поток, ожидая другого: он фактически занимает дополнительный поток.

Итак, поскольку вы a) не используете потоки в полезной манере и b) получаете исключение для своих усилий, я предлагаю вам взять в любом месте вашего кода, который использует эту конструкцию и заменяет ее обычным вызовом метода.

Edit:

Проблемы с потоками в стороне, вы можете попробовать

$content.Add($streamContent)
$content.LoadIntoBufferAsync().GetAwaiter().GetResult()

чтобы заставить поток контента загружаться до следующей операции async post.

Ответ 3

Будьте осторожны - этот NotSupportedException может маскировать другую ошибку, например, проблему авторизации (например, неправильные учетные данные, переданные в объекте NetworkCredential).

Я нашел это, запустив Fiddler и посмотрев ответы.