简体   繁体   中英

IOException on streamed file upload via WCF over HTTP

I'm building a WCF service hosted that allows the client to upload files over HTTP. The service reads the client's Stream chunk by chunk. This works well for a small file, where only a single iteration is needed. But when uploading larger files after some chunks I get an IOException saying An exception has been thrown when reading the stream. on Stream.EndRead() .

The inner exception is The I/O operation has been aborted because of either a thread exit or an application request.

The amount of read chunks varies but I can't really figure out, what causes the difference. The time it works varies from 300ms to 550 ms and ~1MB up to ~2MB processed.

Does anyone have a clue?

The interface is defined like this:

[ServiceContract]
  public interface IServiceFileserver
  {
    [OperationContract]
    UploadResponse UploadFile(UploadRequest uploadRequest);

    // All status feedback related code is left out for simplicity
    // [OperationContract]
    // RunningTaskStatus GetProgress(Guid taskId); 
  }

  [MessageContract]
  public class UploadRequest
  {
    [MessageHeader()]
    public string FileName { get; set; }

    [MessageHeader()]
    public long SizeInByte { get; set; }

    [MessageBodyMember(Order = 1)]
    public Stream Stream { get; set; }
  }

  [MessageContract]
  public class UploadResponse
  {
    [MessageBodyMember()]
    public Guid TaskId { get; set; }
  }

Here is the service implementation:

const int bufferSize = 4 * 1024;
// This is called from the client side
public UploadResponse UploadFile(UploadRequest uploadRequest)
{
  Guid taskId = Guid.NewGuid();
  Stream stream = null;
  try
  {
    stream = uploadRequest.Stream;
    string filename = uploadRequest.FileName;
    long sizeInBytes = uploadRequest.SizeInByte;
    byte[] buffer = new byte[bufferSize];

    stream.BeginRead(buffer, 0, bufferSize, ReadAsyncCallback, new AsyncHelper(buffer, stream, sizeInBytes));
  }
  catch (Exception ex)
  {
    if (stream != null)
      stream.Close();
  }
  return new UploadResponse() { TaskId = taskId };
}

// Helper class for the async reading
public class AsyncHelper
{
  public Byte[] ByteArray { get; set; }
  public Stream SourceStream { get; set; }
  public long TotalSizeInBytes { get; set; }
  public long BytesRead { get; set; }

  public AsyncHelper(Byte[] array, Stream sourceStream, long totalSizeInBytes)
  {
    this.ByteArray = array;
    this.SourceStream = sourceStream;
    this.TotalSizeInBytes = totalSizeInBytes;
    this.BytesRead = 0;
  }
}

// Internal reading of a chunk from the stream
private void ReadAsyncCallback(IAsyncResult ar)
{
  AsyncHelper info = ar.AsyncState as AsyncHelper;
  int amountRead = 0;
  try
  {
    amountRead = info.SourceStream.EndRead(ar);
  }
  catch (IOException ex)
  {
    Trace.WriteLine(ex.Message);
    info.SourceStream.Close();
    return;
  }

  // Do something with the stream
  info.BytesRead += amountRead;
  Trace.WriteLine("info.BytesRead: " + info.BytesRead);

  if (info.SourceStream.Position < info.TotalSizeInBytes)
  {
    try
    { // Read next chunk from stream
      info.SourceStream.BeginRead(info.ByteArray, 0, info.ByteArray.Length, ReadAsyncCallback, info);
    }
    catch (IOException ex)
    {
      info.SourceStream.Close();
    }
  }
  else
  {
    info.SourceStream.Close();     
  }
}

The binding is defined like this:

BasicHttpBinding binding = new BasicHttpBinding();
binding.TransferMode = TransferMode.Streamed;
binding.MessageEncoding = WSMessageEncoding.Mtom;
binding.MaxReceivedMessageSize = 3 * 1024 * 1024;
binding.MaxBufferSize = 64 * 1024;
binding.CloseTimeout = new TimeSpan(0, 1, 0);
binding.OpenTimeout = new TimeSpan(0, 1, 0);
binding.ReceiveTimeout = new TimeSpan(0, 10, 0);
binding.SendTimeout = new TimeSpan(0, 1, 0);
binding.Security.Mode = BasicHttpSecurityMode.None;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

uploadRequest.Stream is a Stream that is provided by WCF. This stream probably feeds off the underlying TCP connection that WCF maintains with the client of your service.

This stream is not the same object instance that your service client has passed in. That would be impossible because client and server are only connected over TCP. They do not share the same address space so they cannot share object instances.

You are returning from UploadFile before the stream has been fully processed. WCF cannot know that a background thread of yours is still using this Stream object. So WCF releases the resources underlying the stream (probably it closes the TCP connection to the client).

WCF will close that stream once your request handling method has returned. Your async processing will then non-deterministically fail. It is a threading race between you using the stream and WCF racing to close it.

The comments under the question show that there is a misunderstanding somewhere but I'm not exactly sure what it is. If you need further clarification please leave a comment and say what you disagree with and why.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM