System.Net.HttpClient 上传文件抛出:该流不支持并发IO读写操作

Lal*_*ale 5 .net c# upload multithreading dotnet-httpclient

我正在开发一个项目,该项目有一些跑步者,每个跑步者都可以同时上传文件。任意地我会得到一个“流不支持并发 IO 读或写操作”错误。

我们已经看到它发生在小文件(5Mb~10Mb)和大文件(1Gb~2Gb)上。我们甚至尝试了完全相同的文件,有时我们能够重现它。

我们的设置具有 NTLM 身份验证、自签名证书并使用 OWIN/Katana 自托管。全部使用 .Net 编写。

它只发生在(到目前为止)在虚拟机上,我们还没有使用物理机来解决这个问题,尽管其中一些虚拟机真的很强大

这是我的代码:

客户

public Guid Upload(string filePath, Guid taskId)
{
    if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException("Value cannot be empty.", "filePath"); }

    var key = Guid.Empty;
    var fileInfo = new System.IO.FileInfo(filePath);

    using (var fileStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
    {
        var content = new StreamContent(fileStream);
        content.Headers.Add("TaskId", taskId.ToString());
        content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        content.Headers.ContentDisposition.FileName = fileInfo.Name;
        content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        content.Headers.ContentLength = fileInfo.Length;

        key = HttpClient.PostGet<Guid>(content, "https://localhost:1234/Files/Upload");
    }

    return key;
}

public T PostGet<T>(string url, HttpContent content, IDictionary<string, string> headers)
{
    using (var client = httpClient) // <-- look below to check how we build this
    {
        client.AddHeaders(headers);
        HttpResponseMessage response = null;

        try
        {
            response = client.PostAsync(url, content).Result;

            ensureSuccessStatusCode(response);

            var jsonResult = response.Content.ReadAsStringAsync().Result;
            var obj = Deserialize<T>(jsonResult);

            return obj;
        }
        finally
        {
            if (response != null)
            {
                response.Dispose();
            }
        }
    }
}

private System.Net.Http.HttpClient httpClient
{
    get
    {
        System.Net.Http.HttpClient client = null;

        if (authenticationEnabled)
        {
            if (credentials == null)
            {
                client = new System.Net.Http.HttpClient(new CertValidatingWebRequestHandler()
                {
                    UseDefaultCredentials = true
                });
            }
            else
            {
                client = new System.Net.Http.HttpClient(new CertValidatingWebRequestHandler()
                {
                    Credentials = credentials
                });
            }
        }
        else
        {
            client = new System.Net.Http.HttpClient();
        }

        client.Timeout = TimeSpan.FromMilliseconds(TimeOutInMilliseconds);
        return client;
    }
}

public class CertValidatingWebRequestHandler : WebRequestHandler
{
    public CertValidatingWebRequestHandler()
    {
        this.ServerCertificateValidationCallback = ValidateRemoteCert;
    }

    private bool ValidateRemoteCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyErrors)
    {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

服务器

[HttpPost]
[Route("Upload")]
public async Task<IHttpActionResult> Upload()
{
    return await upload(Guid.Empty);
}

private async Task<IHttpActionResult> upload(Guid appendToKey)
{
    if (Request.Content.Headers.ContentType.MediaType.Equals("application/octet-stream", StringComparison.OrdinalIgnoreCase))
    {
        var taskId = Guid.Empty;

        if (Request.Headers.Contains("TaskId"))
        {
            var taskIdValue = Request.Headers.GetValues("TaskId").FirstOrDefault();
            taskId = new Guid(taskIdValue);
        }

        var fileName = Request.Content.Headers.ContentDisposition.FileName;
        fileName = fileName.Trim(' ', '"');

        if (string.IsNullOrEmpty(fileName))
        {
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }

        var key = Guid.NewGuid();
        var directoryPath = string.Empty;

        if (appendToKey == Guid.Empty)
        {
            directoryPath = createStorageDirectory(key);
        }
        else
        {
            var fileInfo = fileStorage.GetFileInfo(appendToKey);

            if (fileInfo == null)
            {
                throw new Exception(string.Format("File with key {0} was not found.", appendToKey));
            }

            directoryPath = fileInfo.Path;
        }

        var filePath = fileSystem.Combine(directoryPath, fileName);

        using (var httpStream = await Request.Content.ReadAsStreamAsync())
        {
            using (var fileStream = new FileStream(filePath, FileMode.CreateNew))
            {
                await httpStream.CopyToAsync(fileStream).ContinueWith(t =>
                {
                    try
                    {
                        handleResponse(key, directoryPath, fileName, appendToKey, taskId, t);
                    }
                    catch (Exception e)
                    {
                        deleteStorageFolder(key);

                        logger.Log(LogType.Error, e);
                        throw;
                    }
                });
            }
        }

        return Ok(key);
    }
    else
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }
}
Run Code Online (Sandbox Code Playgroud)

完全错误

System.AggregateException: One or more errors occurred. ---> System.NotSupportedException: The stream does not support concurrent IO read or write operations.
       at System.Net.ConnectStream.InternalWrite(Boolean async, Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
       at System.Net.ConnectStream.BeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
       at System.Net.Http.StreamToStreamCopy.TryStartWriteSync(Int32 bytesRead)
       at System.Net.Http.StreamToStreamCopy.BufferReadCallback(IAsyncResult ar)
       --- End of inner exception stack trace ---
    ---> (Inner Exception #0) System.NotSupportedException: The stream does not support concurrent IO read or write operations.
       at System.Net.ConnectStream.InternalWrite(Boolean async, Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
       at System.Net.ConnectStream.BeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)
       at System.Net.Http.StreamToStreamCopy.TryStartWriteSync(Int32 bytesRead)
       at System.Net.Http.StreamToStreamCopy.BufferReadCallback(IAsyncResult ar)<---
Run Code Online (Sandbox Code Playgroud)

很抱歉这篇很长的帖子,但我认为信息越多对你越好。

我正在尝试禁用身份验证和证书以进一步隔离问题。稍后我会更新。

提前致谢。任何帮助/建议/想法都非常受欢迎!!!!