2010-09-08 10 views

Respuesta

33
bloque

¿El Console.WriteLine hasta que la salida se ha escrito o se volver inmediatamente?

Sí.

Si lo hace el bloque hay un método de escritura salida asíncrona al consola?

La solución para escribir en la consola sin bloquear es sorprendentemente trivial si está utilizando .NET 4.0. La idea es poner en cola los valores de texto y dejar que un solo hilo dedicado haga las llamadas Console.WriteLine. El patrón productor-consumidor es ideal aquí porque preserva el orden temporal que está implícito cuando se usa la clase nativa Console. La razón por la cual .NET 4.0 lo hace fácil es porque tiene la clase BlockingCollection que facilita la producción de un patrón productor-consumidor. Si no está utilizando .NET 4.0, puede obtener un backport descargando el marco Reactive Extensions.

public static class NonBlockingConsole 
{ 
    private static BlockingCollection<string> m_Queue = new BlockingCollection<string>(); 

    static NonBlockingConsole() 
    { 
    var thread = new Thread(
    () => 
     { 
     while (true) Console.WriteLine(m_Queue.Take()); 
     }); 
    thread.IsBackground = true; 
    thread.Start(); 
    } 

    public static void WriteLine(string value) 
    { 
    m_Queue.Add(value); 
    } 
} 
+3

Me gusta su sugerencia ya que evita la creación de un buffer intermedio para los bytes de la cadena. –

+0

Puede mejorar aún más esto mediante el uso de un subproceso de subprocesos en lugar de crear un subproceso nuevo. Los hilos son caros de crear y destruir. Usar un hilo de subproceso evita esto. http://msdn.microsoft.com/en-us/library/4yd16hza.aspx – Aniket

+7

@Aniket: Eso no funcionaría muy bien porque la consola se actualizaría fuera de servicio. La mejor manera de garantizar que las escrituras ocurran en el mismo orden en que se emitieron es usar una única cadena separada. El gasto de recursos es insignificante ya que 1) solo se crea 1 hilo y 2) vive para siempre. –

-1

Sí bloquea. Y no hay escrituras de consola asincrónicas integradas en el marco que yo sepa.

+2

http: // stackoverflow .com/a/25895151/2878353 –

+1

Eso es genial ... mira la fecha de mi respuesta r. 'async' /' await' y 'Console. * Async' no existían en 2010. –

+2

No dije que lo hizo brother –

0

Una prueba rápida en mi escritorio sugeriría que sí, bloquea.

Para escribir de forma asíncrona en la consola, puede simplemente enviar sus escrituras a otro hilo que luego podría escribirlas. Podrías hacer eso teniendo otro hilo girando que tenga una cola de mensajes para escribir, o encadenando instancias Task para que tus escrituras sean ordenadas.

Mi sugerencia anterior de utilizar el grupo de subprocesos es mala, ya que no garantiza el orden y, por lo tanto, la salida de la consola podría estar mezclada.

+0

Esto funciona, pero el trabajo de inicio en el threadpool tiene una semántica pobre para ordenar la consola. Incluso si lanzo workitem A y workitem B desde el mismo hilo, no hay garantía de que se ejecutará primero. – blucz

+1

@blucz: Ese es un muy buen punto. No pensé en eso. –

8

Sí, Console.WriteLine se bloqueará hasta que se escriba la salida, ya que llama al método de escritura de la instancia de la secuencia subyacente. Puede escribir de forma asíncrona a la transmisión de salida estándar (que se conoce como transmisión subyacente) llamando a Console.OpenStandardOutput para obtener la transmisión, luego llamando a BeginWrite y EndWrite en esa transmisión; tenga en cuenta que tendrá que hacer su propia codificación de cadena (convirtiendo el objeto System.String en un byte []) ya que las secuencias solo aceptan matrices de bytes.

Editar - un código de ejemplo para empezar:

using System; 
using System.IO; 
using System.Text; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     string text = "Hello World!"; 

     Stream stdOut = Console.OpenStandardOutput(); 

     byte[] textAsBytes = Encoding.UTF8.GetBytes(text); 

     IAsyncResult result = stdOut.BeginWrite(textAsBytes, 0, textAsBytes.Length, callbackResult => stdOut.EndWrite(callbackResult), null); 

     Console.ReadLine(); 
    } 
} 

Editar 2 - Una versión alternativa, en base a la sugerencia de Brian Gedeón en los comentarios (tenga en cuenta que esto sólo cubre uno de los dieciséis sobrecargas de escritura & WriteLine que están disponibles)

Implementar Begin/End métodos como extensiones de la clase TextWriter, a continuación, añadir una clase AsyncConsole llamarlos:

using System; 
using System.IO; 
using System.Threading.Tasks; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     string text = "Hello World!"; 

     AsyncConsole.WriteLine(text); 

     Console.ReadLine(); 
    } 
} 

public static class TextWriterExtensions 
{ 
    public static IAsyncResult BeginWrite(this TextWriter writer, string value, AsyncCallback callback, object state) 
    { 
     return Task.Factory.StartNew(x => writer.Write(value), state).ContinueWith(new Action<Task>(callback)); 
    } 

    public static void EndWrite(this TextWriter writer, IAsyncResult result) 
    { 
     var task = result as Task; 

     task.Wait(); 
    } 

    public static IAsyncResult BeginWriteLine(this TextWriter writer, string value, AsyncCallback callback, object state) 
    { 
     return Task.Factory.StartNew(x => writer.WriteLine(value), state).ContinueWith(new Action<Task>(callback)); 
    } 

    public static void EndWriteLine(this TextWriter writer, IAsyncResult result) 
    { 
     var task = result as Task; 

     task.Wait(); 
    } 
} 

public static class AsyncConsole 
{ 
    public static IAsyncResult Write(string value) 
    { 
     return Console.Out.BeginWrite(value, callbackResult => Console.Out.EndWrite(callbackResult), null); 
    } 

    public static IAsyncResult WriteLine(string value) 
    { 
     return Console.Out.BeginWriteLine(value, callbackResult => Console.Out.EndWriteLine(callbackResult), null); 
    } 
} 

Editar 3

Alternativamente, escribir un TextWriter asíncrono, inyectarlo usando Console.SetOut y luego llamar a Console.WriteLine exactamente como normales.

El TextWriter sería algo así como:

using System; 
using System.IO; 
using System.Text; 
using System.Threading.Tasks; 

public class AsyncStreamWriter 
    : TextWriter 
{ 
    private Stream stream; 
    private Encoding encoding; 

    public AsyncStreamWriter(Stream stream, Encoding encoding) 
    { 
     this.stream = stream; 
     this.encoding = encoding; 
    } 

    public override void Write(char[] value, int index, int count) 
    { 
     byte[] textAsBytes = this.Encoding.GetBytes(value, index, count); 

     Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, textAsBytes, 0, textAsBytes.Length, null); 
    } 

    public override void Write(char value) 
    { 
     this.Write(new[] { value }); 
    } 

    public static void InjectAsConsoleOut() 
    { 
     Console.SetOut(new AsyncStreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding)); 
    } 

    public override Encoding Encoding 
    { 
     get 
     { 
      return this.encoding; 
     } 
    } 
} 

Tenga en cuenta que he cambiado a usar tareas para gestionar los métodos begin/end: esto se debe a que el método BeginWrite sí parece bloquear si ya hay un asíncrono escribir en progreso.

Una vez que tenga esa clase, solo llame al método de inyección y cada llamada que realice a Console.WriteLine, independientemente de dónde lo haga o con qué sobrecarga, se volverá asincrónica. Comme ca:

class Program 
{ 
    static void Main(string[] args) 
    { 
     string text = "Hello World!"; 

     AsyncStreamWriter.InjectAsConsoleOut(); 

     Console.WriteLine(text); 

     Console.ReadLine(); 
    } 
} 
+0

Su sugerencia es notable y aplicable, sin embargo, en mi situación, me gustaría evitar asignar un búfer temporal. –

+0

Es cierto que sería útil si TextWriters implementara el Modelo de programación asíncrono como lo hacen las transmisiones; la ventaja de este enfoque es que no tiene que crear un hilo (lo cual es sorprendentemente caro). – FacticiusVir

+0

Lástima que no puede crear métodos de extensión estáticos. De lo contrario, eso hubiera sido perfecto para encapsular tu lógica. Imagina llamar a 'Console.BeginWriteLine' y similares. Eso sería increíblemente útil y elegante. –

0

Sí, bloqueará hasta que se escriba la salida en la pantalla. No estoy seguro de que esto se indique explícitamente en la documentación, pero puede verificarlo al profundizar en la clase Console en el reflector.En particular, el método InitializeStdOutError(). Al crear el TextWriter para la secuencia de salida, establece AutoFlush en true

6

Hmya, la salida de la consola no es particularmente rápida. Pero este es un 'problema' que nunca necesita ser arreglado. Mantenga sus ojos en el premio: está escribiendo en la consola para el beneficio de un humano. Y esa persona no está cerca para poder leer tan rápido.

Si la salida se redirige, deja de ser lenta. Si eso todavía tiene un impacto en su programa, entonces tal vez esté escribiendo manera demasiada información. Escribir en un archivo en su lugar.

+1

Pero gracias al hecho de que al hacer clic en una consola ingresa el modo "QuickEdit" que hace que Console.WriteLine bloquee hasta salir del modo QuickEdit, tener una consola asíncrona WriteLine es increíblemente agradable (y casi seguro es lo que desea). Con él, un usuario no cuelga accidentalmente su programa o uno de sus subprocesos porque hace una Console.WriteLine(). – aggieNick02

20

A partir de .NET 4.5, TextWriter soporta los métodos WriteAsync y WriteLineAsync, por lo que ahora se puede utilizar:

Console.Out.WriteAsync("...");

y

Console.Out.WriteLineAsync("...");

+0

¿Has probado esto? ¿Realmente funciona? :) Gracias – zsf222

+0

Lo estoy usando ahora - ¡funciona como un encanto! –

+1

bloques para mí incluso cuando se usa Console.Out.WriteLineAsync(); Tenga en cuenta que bloquea cuando estoy esperando la entrada de consola usando Console.ReadKey(); – user1275154

Cuestiones relacionadas