.NET асинхронный поток чтения / записи
я пытался решить это экзаменационное упражнение" параллельное программирование " (на C#):
зная, что
Stream
классеint Read(byte[] buffer, int offset, int size)
иvoid Write(byte[] buffer, int offset, int size)
методы, реализуемые в C#NetToFile
метод, который копирует все данные, полученные отNetworkStream net
экземплярFileStream file
экземпляра. Для передачи используйте асинхронные чтения и синхронные записи, избегая блокировки одного потока во время операций чтения. Передача заканчивается, когдаnet
операция чтения возвращает значение 0. Для упрощения, нет необходимости поддерживать контролируемую отмену операции.
void NetToFile(NetworkStream net, FileStream file);
я пытался решить это упражнение, но я борюсь с вопросом, связанным с самим вопросом. Но сначала, вот мой код:
public static void NetToFile(NetworkStream net, FileStream file) {
byte[] buffer = new byte[4096]; // buffer with 4 kB dimension
int offset = 0; // read/write offset
int nBytesRead = 0; // number of bytes read on each cycle
IAsyncResult ar;
do {
// read partial content of net (asynchronously)
ar = net.BeginRead(buffer,offset,buffer.Length,null,null);
// wait until read is completed
ar.AsyncWaitHandle.WaitOne();
// get number of bytes read on each cycle
nBytesRead = net.EndRead(ar);
// write partial content to file (synchronously)
fs.Write(buffer,offset,nBytesRead);
// update offset
offset += nBytesRead;
}
while( nBytesRead > 0);
}
вопрос, который у меня есть, заключается в том, что в постановке вопроса сказано:
для передачи используйте асинхронный читает и синхронно пишет, избегая один поток, который будет заблокирован во время читать операции
я не совсем уверен, что мое решение выполняет то, что требуется в этом упражнении, потому что я использую AsyncWaitHandle.WaitOne()
дождаться завершения асинхронного чтения.
С другой стороны, я действительно не понимаю, что означает "неблокирующее" решение в этом сценарии, как FileStream
запись должна выполняться синхронно... и чтобы сделать это, я должен подождать, пока NetworkStream
чтение завершается, чтобы продолжить FileStream
написание, не так ли?
не могли бы вы, пожалуйста, помочь мне с этим?
[ EDIT 1]используя обратный звонок решение
хорошо, если я понял, что Продавцов Митчел и willvv ответил, Мне посоветовали использовать метод обратного вызова, чтобы превратить это в "неблокирующее решение". Вот мой код:
byte[] buffer; // buffer
public static void NetToFile(NetworkStream net, FileStream file) {
// buffer with same dimension as file stream data
buffer = new byte[file.Length];
//start asynchronous read
net.BeginRead(buffer,0,buffer.Length,OnEndRead,net);
}
//asynchronous callback
static void OnEndRead(IAsyncResult ar) {
//NetworkStream retrieve
NetworkStream net = (NetworkStream) ar.IAsyncState;
//get number of bytes read
int nBytesRead = net.EndRead(ar);
//write content to file
//... and now, how do I write to FileStream instance without
//having its reference??
//fs.Write(buffer,0,nBytesRead);
}
как вы, возможно, заметили, я застрял в методе обратного вызова, так как я не имейте ссылку на FileStream
экземпляр, где я хочу вызвать "Write(...)" метод.
кроме того, это не потокобезопасное решение, как byte[]
поле выставляется и может быть разделено между параллельными NetToFile
вызовы. Я не знаю, как решить эту проблему, не подвергая это byte[]
поле во внешней области... и я почти уверен, что он не может быть раскрыт таким образом.
я не хочу использовать лямбда и анонимный метод решения, потому что это не в учебной программе курса" параллельное программирование".
6 ответов
вам нужно будет использовать обратный вызов из чтения NetStream для обработки этого. И, честно говоря, было бы проще обернуть логику копирования в свой собственный класс, чтобы вы могли поддерживать экземпляр активных потоков.
вот как я бы подошел к нему (не тестировался):
public class Assignment1
{
public static void NetToFile(NetworkStream net, FileStream file)
{
var copier = new AsyncStreamCopier(net, file);
copier.Start();
}
public static void NetToFile_Option2(NetworkStream net, FileStream file)
{
var completedEvent = new ManualResetEvent(false);
// copy as usual but listen for completion
var copier = new AsyncStreamCopier(net, file);
copier.Completed += (s, e) => completedEvent.Set();
copier.Start();
completedEvent.WaitOne();
}
/// <summary>
/// The Async Copier class reads the input Stream Async and writes Synchronously
/// </summary>
public class AsyncStreamCopier
{
public event EventHandler Completed;
private readonly Stream input;
private readonly Stream output;
private byte[] buffer = new byte[4096];
public AsyncStreamCopier(Stream input, Stream output)
{
this.input = input;
this.output = output;
}
public void Start()
{
GetNextChunk();
}
private void GetNextChunk()
{
input.BeginRead(buffer, 0, buffer.Length, InputReadComplete, null);
}
private void InputReadComplete(IAsyncResult ar)
{
// input read asynchronously completed
int bytesRead = input.EndRead(ar);
if (bytesRead == 0)
{
RaiseCompleted();
return;
}
// write synchronously
output.Write(buffer, 0, bytesRead);
// get next
GetNextChunk();
}
private void RaiseCompleted()
{
if (Completed != null)
{
Completed(this, EventArgs.Empty);
}
}
}
}
даже если это идет против зерна, чтобы помочь людям с их домашней работой, учитывая, что это более года, вот правильный способ сделать это. Все, что вам нужно перекрытие ваши операции чтения / записи - не требуется создание дополнительных потоков или что-либо еще.
public static class StreamExtensions
{
private const int DEFAULT_BUFFER_SIZE = short.MaxValue ; // +32767
public static void CopyTo( this Stream input , Stream output )
{
input.CopyTo( output , DEFAULT_BUFFER_SIZE ) ;
return ;
}
public static void CopyTo( this Stream input , Stream output , int bufferSize )
{
if ( !input.CanRead ) throw new InvalidOperationException( "input must be open for reading" );
if ( !output.CanWrite ) throw new InvalidOperationException( "output must be open for writing" );
byte[][] buf = { new byte[bufferSize] , new byte[bufferSize] } ;
int[] bufl = { 0 , 0 } ;
int bufno = 0 ;
IAsyncResult read = input.BeginRead( buf[bufno] , 0 , buf[bufno].Length , null , null ) ;
IAsyncResult write = null ;
while ( true )
{
// wait for the read operation to complete
read.AsyncWaitHandle.WaitOne() ;
bufl[bufno] = input.EndRead(read) ;
// if zero bytes read, the copy is complete
if ( bufl[bufno] == 0 )
{
break ;
}
// wait for the in-flight write operation, if one exists, to complete
// the only time one won't exist is after the very first read operation completes
if ( write != null )
{
write.AsyncWaitHandle.WaitOne() ;
output.EndWrite(write) ;
}
// start the new write operation
write = output.BeginWrite( buf[bufno] , 0 , bufl[bufno] , null , null ) ;
// toggle the current, in-use buffer
// and start the read operation on the new buffer.
//
// Changed to use XOR to toggle between 0 and 1.
// A little speedier than using a ternary expression.
bufno ^= 1 ; // bufno = ( bufno == 0 ? 1 : 0 ) ;
read = input.BeginRead( buf[bufno] , 0 , buf[bufno].Length , null , null ) ;
}
// wait for the final in-flight write operation, if one exists, to complete
// the only time one won't exist is if the input stream is empty.
if ( write != null )
{
write.AsyncWaitHandle.WaitOne() ;
output.EndWrite(write) ;
}
output.Flush() ;
// return to the caller ;
return ;
}
public static async Task CopyToAsync( this Stream input , Stream output )
{
await input.CopyToAsync( output , DEFAULT_BUFFER_SIZE ) ;
return;
}
public static async Task CopyToAsync( this Stream input , Stream output , int bufferSize )
{
if ( !input.CanRead ) throw new InvalidOperationException( "input must be open for reading" );
if ( !output.CanWrite ) throw new InvalidOperationException( "output must be open for writing" );
byte[][] buf = { new byte[bufferSize] , new byte[bufferSize] } ;
int[] bufl = { 0 , 0 } ;
int bufno = 0 ;
Task<int> read = input.ReadAsync( buf[bufno] , 0 , buf[bufno].Length ) ;
Task write = null ;
while ( true )
{
await read ;
bufl[bufno] = read.Result ;
// if zero bytes read, the copy is complete
if ( bufl[bufno] == 0 )
{
break;
}
// wait for the in-flight write operation, if one exists, to complete
// the only time one won't exist is after the very first read operation completes
if ( write != null )
{
await write ;
}
// start the new write operation
write = output.WriteAsync( buf[bufno] , 0 , bufl[bufno] ) ;
// toggle the current, in-use buffer
// and start the read operation on the new buffer.
//
// Changed to use XOR to toggle between 0 and 1.
// A little speedier than using a ternary expression.
bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ;
read = input.ReadAsync( buf[bufno] , 0 , buf[bufno].Length );
}
// wait for the final in-flight write operation, if one exists, to complete
// the only time one won't exist is if the input stream is empty.
if ( write != null )
{
await write;
}
output.Flush();
// return to the caller ;
return;
}
}
Ура.
Я сомневаюсь, что это самый быстрый код (есть некоторые накладные расходы от абстракции задачи .NET), но я думаю, что это пылесос подход ко всей асинхронной копии.
мне нужно CopyTransformAsync
где я мог передать делегата, чтобы сделать что-то, поскольку куски были переданы через операцию копирования. например, вычислить дайджест сообщения при копировании. Вот почему я заинтересовался своим собственным. выбор.
выводы:
- CopyToAsync bufferSize чувствителен (требуется большой буфер)
- FileOptions.Асинхронный -> делает его ужасно медленным (не уверен, почему это так)
- размер буфера объектов FileStream может быть меньше (это не так важно)
- на
Serial
тест, Безусловно, самый быстрый и наиболее ресурсоемкий
вот что я нашел и код для программы, которую я использовал для тестирования этого. На моей машине эти тесты были запущены на SSD-диске и эквивалентны копии файла. Обычно вы не хотите использовать это для простого копирования файлов, вместо этого, когда у вас есть сетевой поток (что является моим вариантом использования), вот когда вы хотите использовать что-то вроде этого.
4K buffer
Serial... in 0.474s
CopyToAsync... timed out
CopyToAsync (Asynchronous)... timed out
CopyTransformAsync... timed out
CopyTransformAsync (Asynchronous)... timed out
8K buffer
Serial... in 0.344s
CopyToAsync... timed out
CopyToAsync (Asynchronous)... timed out
CopyTransformAsync... in 1.116s
CopyTransformAsync (Asynchronous)... timed out
40K buffer
Serial... in 0.195s
CopyToAsync... in 0.624s
CopyToAsync (Asynchronous)... timed out
CopyTransformAsync... in 0.378s
CopyTransformAsync (Asynchronous)... timed out
80K buffer
Serial... in 0.190s
CopyToAsync... in 0.355s
CopyToAsync (Asynchronous)... in 1.196s
CopyTransformAsync... in 0.300s
CopyTransformAsync (Asynchronous)... in 0.886s
160K buffer
Serial... in 0.432s
CopyToAsync... in 0.252s
CopyToAsync (Asynchronous)... in 0.454s
CopyTransformAsync... in 0.447s
CopyTransformAsync (Asynchronous)... in 0.555s
здесь вы можете увидеть Process Explorer, график производительности при запуске теста. В основном каждый top (в нижний из трех графиков) является началом серийного теста. Вы можете ясно видеть, как резко увеличивается пропускная способность по мере увеличения размера буфера. Казалось бы, он планирует где-то около 80K, что является то, что .NET framework CopyToAsync
метод использует, внутренне.
хорошая вещь здесь в том, что окончательная реализация не была такой сложной:
static Task CompletedTask = ((Task)Task.FromResult(0));
static async Task CopyTransformAsync(Stream inputStream
, Stream outputStream
, Func<ArraySegment<byte>, ArraySegment<byte>> transform = null
)
{
var temp = new byte[bufferSize];
var temp2 = new byte[bufferSize];
int i = 0;
var readTask = inputStream
.ReadAsync(temp, 0, bufferSize)
.ConfigureAwait(false);
var writeTask = CompletedTask.ConfigureAwait(false);
for (; ; )
{
// synchronize read
int read = await readTask;
if (read == 0)
{
break;
}
if (i++ > 0)
{
// synchronize write
await writeTask;
}
var chunk = new ArraySegment<byte>(temp, 0, read);
// do transform (if any)
if (!(transform == null))
{
chunk = transform(chunk);
}
// queue write
writeTask = outputStream
.WriteAsync(chunk.Array, chunk.Offset, chunk.Count)
.ConfigureAwait(false);
// queue read
readTask = inputStream
.ReadAsync(temp2, 0, bufferSize)
.ConfigureAwait(false);
// swap buffer
var temp3 = temp;
temp = temp2;
temp2 = temp3;
}
await writeTask; // complete any lingering write task
}
этот метод чередования чтения/записи, несмотря на огромных буферов где-то между 18% быстрее, чем BCL CopyToAsync
.
из любопытства я изменил асинхронные вызовы на типичные вызовы begin / end async pattern, и это не улучшило ситуацию ни на йоту, это сделало ее хуже. Для всех, кого я люблю колотить по абстракции задач, они делают некоторые изящные вещи, когда вы пишете код с ключевыми словами async/await, и гораздо приятнее читать этот код!
Вау, все это очень сложно! Вот мое асинхронное решение, и это всего лишь одна функция. Read () и BeginWrite () выполняются одновременно.
/// <summary>
/// Copies a stream.
/// </summary>
/// <param name="source">The stream containing the source data.</param>
/// <param name="target">The stream that will receive the source data.</param>
/// <remarks>
/// This function copies until no more can be read from the stream
/// and does not close the stream when done.<br/>
/// Read and write are performed simultaneously to improve throughput.<br/>
/// If no data can be read for 60 seconds, the copy will time-out.
/// </remarks>
public static void CopyStream(Stream source, Stream target)
{
// This stream copy supports a source-read happening at the same time
// as target-write. A simpler implementation would be to use just
// Write() instead of BeginWrite(), at the cost of speed.
byte[] readbuffer = new byte[4096];
byte[] writebuffer = new byte[4096];
IAsyncResult asyncResult = null;
for (; ; )
{
// Read data into the readbuffer. The previous call to BeginWrite, if any,
// is executing in the background..
int read = source.Read(readbuffer, 0, readbuffer.Length);
// Ok, we have read some data and we're ready to write it, so wait here
// to make sure that the previous write is done before we write again.
if (asyncResult != null)
{
// This should work down to ~0.01kb/sec
asyncResult.AsyncWaitHandle.WaitOne(60000);
target.EndWrite(asyncResult); // Last step to the 'write'.
if (!asyncResult.IsCompleted) // Make sure the write really completed.
throw new IOException("Stream write failed.");
}
if (read <= 0)
return; // source stream says we're done - nothing else to read.
// Swap the read and write buffers so we can write what we read, and we can
// use the then use the other buffer for our next read.
byte[] tbuf = writebuffer;
writebuffer = readbuffer;
readbuffer = tbuf;
// Asynchronously write the data, asyncResult.AsyncWaitHandle will
// be set when done.
asyncResult = target.BeginWrite(writebuffer, 0, read, null, null);
}
}
странно, что никто не упомянул TPL.
здесьочень хороший пост pfx team (Stephen Toub) о том, как реализовать параллельную копию асинхронного потока. Пост содержит устаревшие справочную образцам так вот допущены один:
Get дополнительные параллельные расширения из кода.в MSDN затем
var task = sourceStream.CopyStreamToStreamAsync(destinationStream);
// do what you want with the task, for example wait when it finishes:
task.Wait();
также рассмотреть возможность использования Ж. богаче по AsyncEnumerator.
вы правы, то, что вы делаете, в основном Синхронное чтение, потому что вы используете метод WaitOne (), и он просто останавливает выполнение, пока данные не будут готовы, это в основном то же самое, что делать это с помощью Read() вместо BeginRead() и EndRead().
Что вам нужно сделать, это использовать аргумент обратного вызова в методе BeginRead (), с ним вы определяете метод обратного вызова (или лямбда-выражение), этот метод будет вызываться, когда информация была прочитана (в обратном вызове метод, который вы должны проверить на конец потока и записать в выходной поток), таким образом, вы не будете блокировать основной поток (Вам не понадобится WaitOne () или EndRead ().
надеюсь, что это помогает.