用 .NET 4.5 編寫的 Windows 服務中的 Console.Out 和 Console.Error 競爭條件錯誤 (Console.Out and Console.Error race condition error in a Windows service written in .NET 4.5)


問題描述

用 .NET 4.5 編寫的 Windows 服務中的 Console.Out 和 Console.Error 競爭條件錯誤 (Console.Out and Console.Error race condition error in a Windows service written in .NET 4.5)

我在生產中遇到了一個奇怪的問題,Windows 服務隨機掛起,希望能在根本原因分析方面提供任何幫助。

該服務是用 C# 編寫的,並部署到使用 .NET 4.5 的機器上(儘管我也可以使用 .NET 4.5.1 重現它)。

錯誤報告的是:

Probable I/O race condition detected while copying memory. 
The I/O package is not thread safe by default. 
In multithreaded applications, a stream must be accessed in a thread‑safe way, such as a thread‑safe wrapper returned by TextReader's or TextWriter's Synchronized methods. 
This also applies to classes like StreamWriter and StreamReader.

我已將異常的來源縮小到在記錄器中調用 Console.WriteLine() 和 Console.Error.WriteLine()。這些從多個線程調用並且在高負載下,開始出現錯誤並且服務掛起。

但是,根據 MSDN 整個 Console 類是線程安全的(我之前在多個線程中使用過它,沒有問題)。更重要的是,這個問題不會在和控制台應用程序運行相同的代碼時出現;僅來自 Windows 服務。最後,異常的堆棧跟踪顯示了在控制台類中對 SyncTextWriter 的內部調用,這應該是異常中提到的同步版本。

有誰知道我做錯了什麼或遺漏了一點這裡?一種可能的解決方法似乎是將 Out 和 Err 流重定向到 /dev/null 但我更喜歡更詳細的分析,這似乎超出了我對 .NET 的了解。

我創建了一個重現 Windows 服務嘗試時會引發錯誤。代碼如下。

服務類:

[RunInstaller(true)]
public partial class ParallelTest : ServiceBase
{
    public ParallelTest()
    {
        InitializeComponent();
        this.ServiceName = "ATestService";
    }

    protected override void OnStart(string[] args)
    {
        Thread t = new Thread(DoWork);
        t.IsBackground = false;

        this.EventLog.WriteEntry("Starting worker thread");
        t.Start();

        this.EventLog.WriteEntry("Starting service");
    }

    protected override void OnStop()
    {
    }

    private void DoWork()
    {
        this.EventLog.WriteEntry("Starting");
        Parallel.For(0, 1000, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, (_) =>
        {
            try
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("test message to the out stream");
                    Thread.Sleep(100);
                    Console.Error.WriteLine("Test message to the error stream");
                }
            }
            catch (Exception ex)
            {
                this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
                //throw;
            }
        });
        this.EventLog.WriteEntry("Finished");
    }
}

主類:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        // Remove comment below to stop the errors
        //Console.SetOut(new StreamWriter(Stream.Null));
        //Console.SetError(new StreamWriter(Stream.Null));

        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] 
        { 
            new ParallelTest() 
        };
        ServiceBase.Run(ServicesToRun);
    }
}

安裝程序類:


參考解法

方法 1:

Console.Out and Console.Error are both thread‑safe as they each return a thread‑safe wrapper (via TextWriter.Synchronized) for the console output and error stream TextWriters. However, this thread‑safety only applies if Console.Out and Console.Error are TextWriters for different streams.

The reason that your code throws an exception when it runs as a Windows service is that in that case, the output and error TextWriters are both set to StreamWriter.Null, which is a singleton. Your code calls both Console.WriteLine and Console.Error.WriteLine and this causes the exception when one thread happens to call Console.WriteLine at the same time that another thread is calling Console.Error.WriteLine. This causes the same stream to be written to from 2 threads at the same time, resulting in the "Probable I/O race condition detected while copying memory." exception. If you only use Console.WriteLine or only use Console.Error.WriteLine, you'll discover that the exception no longer occurs.

Here's a minimal non‑service console program that demonstrates the issue:

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

class Program
{
    static void Main(string[] args)
    {
        var oldOut = Console.Out;
        var oldError = Console.Error;

        Console.SetOut(StreamWriter.Null);
        Console.SetError(StreamWriter.Null);
        Parallel.For(0, 2, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (_) =>
        {
            try
            {
                while(true)
                {
                    Console.WriteLine("test message to the out stream");
                    Console.Error.WriteLine("Test message to the error stream");
                }
            }
            catch (Exception ex)
            {
                Console.SetOut(oldOut);
                Console.SetError(oldError);
                Console.WriteLine(ex);
                Environment.Exit(1);
            }
        });
    }
}

(by braintechduser148673)

參考文件

  1. Console.Out and Console.Error race condition error in a Windows service written in .NET 4.5 (CC BY‑SA 2.5/3.0/4.0)

#Console #windows-services #.net #C#






相關問題

grails 日誌消息未顯示在 STS 3.0 控制台上 (grails log messages not displaying on STS 3.0 console)

在 AppFog 上,如何在終端(node.js)上查看控制台日誌 (on AppFog, how to see the console logs on the terminal (node.js))

控制台應用程序中的多個參數未正確解析 (Multiple args in Console Application not parsing correctly)

WPF 應用程序沒有輸出到控制台? (No output to console from a WPF application?)

用 .NET 4.5 編寫的 Windows 服務中的 Console.Out 和 Console.Error 競爭條件錯誤 (Console.Out and Console.Error race condition error in a Windows service written in .NET 4.5)

Visual Basic 2013 - 控制台輸入令牌? (Visual Basic 2013 - Console Input Tokens?)

帶有 stringbuilder 日曆表的控制台日曆 (Console Calendar with stringbuilder calendar sheet)

增加和減少混淆 (Incrementing and Decrementing Confusion)

有沒有辦法在標準 C++ 中直接從鍵盤讀取輸入? (Is there a way to read input directly from the keyboard in standard C++?)

是否可以在方案中編寫控制台應用程序? (Is it possible to write console applications in scheme?)

犀牛的控制台輸入功能? (console input function for rhino?)

輸出中的 PowerShell 空白 (PowerShell gaps in output)







留言討論