【问题标题】:Does Console.WriteLine block?Console.WriteLine 是否阻塞?
【发布时间】:2011-04-09 20:41:39
【问题描述】:

Console.WriteLine 会阻塞直到输出被写入还是立即返回?

如果确实阻塞了,是否有将异步输出写入控制台的方法?

【问题讨论】:

    标签: .net multithreading console


    【解决方案1】:

    自从 .NET Framework 4.5(即很久以前)以来,您已经能够避免阻塞当前线程:

    await Console.Out.WriteLineAsync("Hello, non-blocking, world!");
    

    【讨论】:

      【解决方案2】:

      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 支持 WriteAsyncWriteLineAsync 方法,因此您现在可以使用:

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

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

      【讨论】:

      • 即使在使用 Console.Out.WriteLineAsync();请注意,当我使用 Console.ReadKey(); 等待控制台输入时它会阻塞;
      • @user1275154 - 如果该方法没有按您的预期工作,那么它可能值得自己提出问题
      • 就时间顺序而言,这有多可靠?
      • 它为每条正在写入的消息创建一个线程,这几乎没有用处。使用队列的实现看起来更好
      • 如果您在每次调用中不使用await,您将在使用它时遇到异常,这可能会破坏您在不等待写入完成的情况下继续执行的目的。如果发生另一个 WriteAsync 时一个 WriteAsync 未完成,则它会引发异常。您必须在每次调用时将对流的访问与await 同步。
      【解决方案3】:

      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);
        }
      }
      

      【讨论】:

      • 我喜欢你的建议,因为它避免了为字符串的字节创建中间缓冲区。
      • 您可以通过使用线程池线程而不是创建新线程来进一步改进这一点。创建和销毁线程的成本很高。使用线程池线程可以避免这种情况。 msdn.microsoft.com/en-us/library/4yd16hza.aspx
      • @Aniket:这不会很好,因为控制台会乱序更新。确保写入按照发出的顺序发生的最佳方法是使用单个单独的线程。资源开销可以忽略不计,因为 1)只创建了 1 个线程,并且 2)它永远存在。
      • 好点,但不会每次都创建一个新线程吗? var thread = new Thread (blah...) 告诉我每次进入 NonBlockingConsole 时都会创建一个新线程,对吧?
      • @Aniket:不...这是一个静态构造函数,因此每个应用程序域只运行一次。
      【解决方案4】:

      是的,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();
          }
      }
      

      【讨论】:

      • 您的建议值得注意且适用,但是,在我的情况下,我想避免分配临时缓冲区。
      • 没错,如果 TextWriters 像 Streams 那样实现异步编程模型会很有帮助;这种方法的优点是您不必创建线程(这样做成本高得惊人)。
      • 太糟糕了,你不能创建静态扩展方法。否则,这将是完美的封装你的逻辑。想象一下打电话给Console.BeginWriteLine 之类的。这将非常有用和优雅。
      • @Brian Gideon:两个新选项,灵感来自您的建议!
      【解决方案5】:

      嗯,控制台输出不是特别快。但这是一个永远不需要解决的“问题”。留意奖品:您正在为人类的利益而写信给控制台。而且那个人还没有读得那么快。

      如果输出被重定向,它就会停止变慢。如果这仍然对您的程序有影响,那么您可能只是在 方式 写了太多信息。改为写入文件。

      【讨论】:

      • 但是由于在控制台中单击会进入“QuickEdit”模式,这会使 Console.WriteLine 阻塞,直到您退出 QuickEdit 模式,拥有异步控制台 WriteLine 非常好(几乎可以肯定你想要什么)。有了它,用户不会因为您执行 Console.WriteLine() 而意外挂起您的程序或其线程之一。
      • 我个人不喜欢这样的答案。要说这不是需要修复的问题,就是假设您知道在每个实例中使用控制台的所有可能方式。如果您正在制作基于控制台的视频游戏,并且您需要它比默认实现更快地运行,该怎么办?
      【解决方案6】:

      在我的办公桌上进行快速测试表明是的,它会阻塞。

      要异步写入控制台,您可以将您的写入发送到另一个线程,然后再将它们写出。您可以通过让另一个线程旋转来实现该操作,该线程具有要写入的消息队列,或者链接Task 实例以便您的写入是有序的。

      我之前使用线程池的建议很糟糕,因为它不能保证顺序,因此您的控制台输出可能会混淆。

      【讨论】:

      • 这行得通,但是在线程池中启动工作对于控制台编写来说排序语义很差。即使我从同一个线程启动工作项 A 和工作项 B,也不能保证哪个会先运行。
      【解决方案7】:

      是的,它会阻塞直到输出被写入屏幕。我不确定这是否在文档中明确说明,但您可以通过挖掘反射器中的 Console 类来验证这一点。特别是InitializeStdOutError() 方法。在为输出流创建TextWriter 时,它将AutoFlush 设置为true

      【讨论】:

        【解决方案8】:

        是的,它会阻塞。我所知道的框架中没有内置异步控制台写入。

        【讨论】:

        • 太好了……看看我回答的日期。 async/awaitConsole.*Async 的东西在 2010 年不存在。
        • 没说是兄弟
        猜你喜欢
        • 2020-09-16
        • 2017-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-02
        • 1970-01-01
        • 2023-03-13
        相关资源
        最近更新 更多