【发布时间】:2011-04-09 20:41:39
【问题描述】:
Console.WriteLine 会阻塞直到输出被写入还是立即返回?
如果确实阻塞了,是否有将异步输出写入控制台的方法?
【问题讨论】:
标签: .net multithreading console
Console.WriteLine 会阻塞直到输出被写入还是立即返回?
如果确实阻塞了,是否有将异步输出写入控制台的方法?
【问题讨论】:
标签: .net multithreading console
自从 .NET Framework 4.5(即很久以前)以来,您已经能够避免阻塞当前线程:
await Console.Out.WriteLineAsync("Hello, non-blocking, world!");
【讨论】:
Console.WriteLine 同步运行,Console.Out 流将在方法返回之前被写入。但是,对于大多数生产用途,您应该使用日志框架而不是标准输出。微软的日志接口明确不包含异步方法,因为"Logging should be so fast that it isn't worth the performance cost of asynchronous code."。
如果您的用例仍然需要写入标准输出并且使用 async/await 的性能成本值得权衡,那么从 .NET 4.5 开始,TextWriter 支持 WriteAsync 和 WriteLineAsync 方法,因此您现在可以使用:
Console.Out.WriteAsync("...");
和
Console.Out.WriteLineAsync("...");
【讨论】:
await,您将在使用它时遇到异常,这可能会破坏您在不等待写入完成的情况下继续执行的目的。如果发生另一个 WriteAsync 时一个 WriteAsync 未完成,则它会引发异常。您必须在每次调用时将对流的访问与await 同步。
Console.WriteLine 是否阻塞直到 输出已被写入或已完成 立即返回?
是的。
如果它确实阻塞,是否有一种方法 将异步输出写入 控制台?
如果您使用的是 .NET 4.0,那么在不阻塞的情况下写入控制台的解决方案非常简单。这个想法是将文本值排队并让一个专用线程执行Console.WriteLine 调用。生产者-消费者模式在这里是理想的,因为它保留了使用本机 Console 类时隐含的时间顺序。 .NET 4.0 使这变得简单的原因是因为它具有BlockingCollection 类,它有助于生产者-消费者模式的产生。如果您不使用 .NET 4.0,则可以通过下载 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);
}
}
【讨论】:
是的,Console.WriteLine 将阻塞直到输出被写入,因为它调用底层流实例的 Write 方法。您可以通过调用 Console.OpenStandardOutput 来异步写入标准输出流(即底层流)以获取流,然后在该流上调用 BeginWrite 和 EndWrite;请注意,您必须自己进行字符串编码(将 System.String 对象转换为 byte[]),因为流只接受字节数组。
编辑 - 一些帮助您入门的示例代码:
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();
}
}
Edit 2 - 一个替代版本,基于 Brian Gideon 在 cmets 中的建议(请注意,这仅涵盖了可用的 16 个 Write 和 WriteLine 重载之一)
实现 Begin/End 方法作为 TextWriter 类的扩展,然后添加 AsyncConsole 类来调用它们:
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);
}
}
编辑 3
或者,编写一个异步 TextWriter,使用 Console.SetOut 注入它,然后像往常一样调用 Console.WriteLine 完全。
TextWriter 类似于:
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;
}
}
}
请注意,我已切换到使用任务来管理 Begin/End 方法:这是因为如果已经有异步写入正在进行,BeginWrite 方法本身似乎会阻塞。
一旦你有了那个类,只需调用注入方法,每个调用你对 Console.WriteLine 的调用,无论你在哪里创建或使用哪个重载,都会变成异步的。商务人士:
class Program
{
static void Main(string[] args)
{
string text = "Hello World!";
AsyncStreamWriter.InjectAsConsoleOut();
Console.WriteLine(text);
Console.ReadLine();
}
}
【讨论】:
Console.BeginWriteLine 之类的。这将非常有用和优雅。
嗯,控制台输出不是特别快。但这是一个永远不需要解决的“问题”。留意奖品:您正在为人类的利益而写信给控制台。而且那个人还没有读得那么快。
如果输出被重定向,它就会停止变慢。如果这仍然对您的程序有影响,那么您可能只是在 方式 写了太多信息。改为写入文件。
【讨论】:
在我的办公桌上进行快速测试表明是的,它会阻塞。
要异步写入控制台,您可以将您的写入发送到另一个线程,然后再将它们写出。您可以通过让另一个线程旋转来实现该操作,该线程具有要写入的消息队列,或者链接Task 实例以便您的写入是有序的。
我之前使用线程池的建议很糟糕,因为它不能保证顺序,因此您的控制台输出可能会混淆。
【讨论】:
是的,它会阻塞直到输出被写入屏幕。我不确定这是否在文档中明确说明,但您可以通过挖掘反射器中的 Console 类来验证这一点。特别是InitializeStdOutError() 方法。在为输出流创建TextWriter 时,它将AutoFlush 设置为true
【讨论】:
是的,它会阻塞。我所知道的框架中没有内置异步控制台写入。
【讨论】:
async/await 和 Console.*Async 的东西在 2010 年不存在。