It's not entirely clear what your goals are with this bit of code. Three possibilities that come to mind:
- CopyStream should be asynchronous. As written, it does not meet this goal since it blocks on the results of EndRead and EndWrite. If this is your goal, you need to use callback methods and not call the End methods until your callbacks have been called. You also need to modify CopyStream's signature to return IAsyncResult and take a callback and state as parameters.
- CopyStream should hide the details of the underlying asynchronous calls from its users, but is itself synchronous. Your code accomplishes this goal, but there are a few things that could use cleaning up.
- CopyStream should hide the details of the underlying asynchronous calls, and also maximize performance by making sure the writer is kept busy at all times and never sitting around idle waiting for reads, to the extent possible. It seems like this may have been your goal due to the attempt to read/write in parallel, but by only allowing a single outstanding read or write, you're not really getting much out of it. In order to implement this correctly, you need to use a reader thread and a writer thread, with a queue of items to be written. The reader reads as fast as it can and inserts chunks of data into the queue, which the writer then writes as fast as it can or as fast as they show up.
EDIT:
As an example of how to write this code using a queue, see the following. Note that I didn't need to spin up a thread because I can just make use of the EndRead/EndWrite callbacks and have the original thread wait for a signal to be raised. Also, this is a lightly-tested first draft, so beware that there could be bugs lurking in there. This type of code is complex and difficult to get right, and should only be used when really necessary for valid perf reasons. If the complexity is not justified, I would just use the synchronous Read and Write methods to simplify the code as much as possible.
public static void CopyStream(Stream input, Stream output)
{
AutoResetEvent completed = new AutoResetEvent(false);
BeginRead(new CopyStreamState(completed, input, output));
completed.WaitOne();
}
private static void BeginRead(CopyStreamState state)
{
const int bufferSize = 8192;
state.InputBuffer = new byte[bufferSize];
state.Input.BeginRead(state.InputBuffer, 0, bufferSize, ReadCallback, state);
}
private static void ReadCallback(IAsyncResult ar)
{
CopyStreamState state = (CopyStreamState)ar.AsyncState;
int bytesRead = state.Input.EndRead(ar);
if (bytesRead > 0)
{
byte[] dataToWrite = state.InputBuffer;
if (bytesRead < state.InputBuffer.Length)
{
dataToWrite = new byte[bytesRead];
Array.Copy(state.InputBuffer, dataToWrite, bytesRead);
}
EnqueueWriteData(state, dataToWrite);
BeginRead(state);
}
else
{
state.FinishedReading = true;
}
BeginWriteOrComplete(state);
}
private static void EnqueueWriteData(CopyStreamState state, byte[] data)
{
lock (state)
{
state.WriteQueue.Enqueue(data);
}
}
private static void BeginWriteOrComplete(CopyStreamState state)
{
lock (state)
{
if (!state.WriteInProgress)
{
if (state.WriteQueue.Count > 0)
{
byte[] outputBuffer = state.WriteQueue.Dequeue();
state.WriteInProgress = true;
state.Output.BeginWrite(outputBuffer, 0, outputBuffer.Length, WriteCallback, state);
}
else if (state.FinishedReading)
{
state.Completed.Set();
}
}
}
}
private static void WriteCallback(IAsyncResult ar)
{
CopyStreamState state = (CopyStreamState)ar.AsyncState;
state.Output.EndWrite(ar);
lock (state)
{
state.WriteInProgress = false;
BeginWriteOrComplete(state);
}
}
private class CopyStreamState
{
public CopyStreamState(
AutoResetEvent completed,
Stream input,
Stream output)
{
this.Completed = completed;
this.Input = input;
this.Output = output;
this.WriteQueue = new Queue<byte[]>();
}
public AutoResetEvent Completed { get; private set; }
public Stream Input { get; private set; }
public Stream Output { get; private set; }
public Queue<byte[]> WriteQueue { get; private set; }
public byte[] InputBuffer { get; set; }
public bool FinishedReading { get; set; }
public bool WriteInProgress { get; set; }
}