【问题标题】:Windows Store App - C# - How to handle multi threaded application debug loggingWindows Store App - C# - 如何处理多线程应用程序调试日志
【发布时间】:2014-03-19 11:21:20
【问题描述】:

我正在尝试编写一个日志类来记录到一个文件,但是由于不同的线程试图同时记录,我一直遇到记录问题。

A first chance exception of type 'System.UnauthorizedAccessException' occurred in mscorlib.dll
System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Log.Diag.<DebugPrint>d__0.MoveNext()

这是我的代码:

        public static async void DebugPrint(string msg, LogLevel level)
    {
        if (ShouldLog(level))
        {
#if DEBUG
            // Only do this in debug
            Debug.WriteLine(msg);
#endif
#if !DEBUG // Never crash in release build
            try
            {
#endif
                if (sFile == null && !(await GetLogFile()))
                {
                    throw new FileNotFoundException("Cannot create ms-appdata:///local/log.txt");
                }
                try
                {
                        await Windows.Storage.FileIO.AppendTextAsync(sFile, ComposeMessage(msg, level));
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
#if !DEBUG
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
#endif
        }
    }

    /// <summary>
    /// Initialise the log file.
    /// </summary>
    /// <returns></returns>
    private async static Task<bool> GetLogFile()
    {
        try
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            sFile = await localFolder.CreateFileAsync("log.txt", CreationCollisionOption.OpenIfExists);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

如何确保所有线程都可以登录到文件?

【问题讨论】:

    标签: c# multithreading file logging windows-store-apps


    【解决方案1】:

    这是我使用事件跟踪的方法。

    Task.cs

    sealed class LogEventSource : EventSource
    {
        public static LogEventSource Log = new LogEventSource();
    
        [Event(1, Level = EventLevel.LogAlways)]
        public void Debug(string message)
        {
            this.WriteEvent(1, message);
        }
    }
    
    /// <summary> 
    /// Storage event listner to do thread safe logging to a file.
    /// </summary> 
    sealed class StorageFileEventListener : EventListener
    {
        private object syncObj = new object();
        private List<string> logLines;
        private StorageFile logFile;
        private ThreadPoolTimer periodicTimer;
    
        public StorageFileEventListener()
        {
            Debug.WriteLine("StorageFileEventListener for {0}", GetHashCode());
            logLines = new List<string>();
        }
        // Should be called right after the constructor (since constructors can't have async calls)
        public async Task InitializeAsync()
        {
            logFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("logs.txt", CreationCollisionOption.OpenIfExists);
    
            // We don't want to write to disk every single time a log event occurs, so let's schedule a
            // thread pool task
            periodicTimer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
            {
                // We have to lock when writing to disk as well, otherwise the in memory cache could change
                // or we might try to write lines to disk more than once
                lock (syncObj)
                {
                    if (logLines.Count > 0)
                    {
                        // Write synchronously here. We'll never be called on a UI thread and you
                        // cannot make an async call within a lock statement
                        FileIO.AppendLinesAsync(logFile, logLines).AsTask().Wait();
                        logLines = new List<string>();
                    }
                }
                CheckLogFile();
    
            }, TimeSpan.FromSeconds(5));
        }
    
    
        private async void CheckLogFile()
        {
    
            BasicProperties p = await logFile.GetBasicPropertiesAsync();
            if(p.Size > (1024 * 1024))
            {
                // TODO: Create new log file and compress old.
            }
        }
    
        protected override void OnEventWritten(EventWrittenEventArgs eventData)
        {
            // This could be called from any thread, and we want our logs in order, so lock here
            lock (syncObj)
            {
                logLines.Add((string)eventData.Payload[0]);
            }
        }
    
    }
    

    包装在一个日志类中。

    /// <summary>
    /// A static class for help with debugging and logging.
    /// </summary>
    public static class Log
    {
        public enum LogLevel {
            NONE = 0,
            FATAL,
            ERROR,
            INFO,
            DEBUG,
            VERBOSE,
            TRACE
        };
    
        private static StorageFileEventListener eventListener;
    #if DEBUG
        public static LogLevel logLevel = LogLevel.DEBUG;
    #else
        public static LogLevel logLevel = LogLevel.NONE;
    #endif
        /// <summary>
        /// Print out the debug message.
        /// </summary>
        /// <param name="msg">Message to print</param>
        /// <param name="level">Debug level of message</param>
        public async static void DebugPrint(string msg, LogLevel level)
        {
            if (ShouldLog(level))
            {
                msg = ComposeMessage(msg, level);
    #if DEBUG
                // Only do this in debug
                Debug.WriteLine(msg);
    #endif
    #if !DEBUG // Never crash in release build
                try
                {
    #endif
                    if (eventListener == null)
                    {
                        eventListener = new StorageFileEventListener();
                        eventListener.EnableEvents(LogEventSource.Log, EventLevel.LogAlways);
                        await eventListener.InitializeAsync();
                    }
                    LogEventSource.Log.Debug(msg);
    #if !DEBUG
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
    #endif
            }
        }
    
        /// <summary>
        /// Construc the formatted log message
        /// </summary>
        /// <param name="msg">Main message</param>
        /// <param name="level">Log level</param>
        /// <returns>Formated message</returns>
        private static string ComposeMessage(string msg, LogLevel level)
        {
            return DateTime.Now.ToString(@"M/d/yyyy hh:mm:ss.fff tt") + " [" + Environment.CurrentManagedThreadId.ToString("X4") + "] " + LevelToString(level) + " " + msg;
        }
    
        /// <summary>
        /// Get the string alias for a log level.
        /// </summary>
        /// <param name="level">The log level</param>
        /// <returns>String representation of the log level.</returns>
        private static string LevelToString(LogLevel level)
        {
            string res = "NOT FOUND";
            switch (level)
            {
                case LogLevel.NONE:
                    throw new Exception("You should not log at this level (NONE)");
                case LogLevel.FATAL: res = "FATAL"; break;
                case LogLevel.ERROR: res = "ERROR"; break;
                case LogLevel.INFO: res = "INFO"; break;
                case LogLevel.DEBUG: res = "DEBUG"; break;
                case LogLevel.VERBOSE: res = "VERBOSE"; break;
                case LogLevel.TRACE: res = "TRACE"; break;
            }
            return res;
        }
    
        /// <summary>
        /// Check the passed log level against the current log level 
        /// to see if the message should be logged.
        /// </summary>
        /// <param name="level">Log level to check against</param>
        /// <returns>True is should be logeed otherwise false.</returns>
        private static bool ShouldLog(LogLevel level)
        {
            if (level <= logLevel)
                return true;
            else
                return false;
        }
    }
    

    用法:

    Log.DebugPrint("Hello, Thread safe logger!", Log.LogLevel.DEBUG);
    

    【讨论】:

      【解决方案2】:

      为了避免并发问题,您需要使用locks

      【讨论】:

      • 但您似乎无法在 lock 声明中使用 await。我正在异步写入文件
      【解决方案3】:

      最好将所有消息写入队列并使用后台线程将队列写入文件。这有很多优点:

      • 轻松实现多线程节省。只需锁定对队列的所有访问权限

      • 只有 1 个线程写入文件 => 不再有多线程问题

      • 添加到队列非常快(微秒)并且几乎不会锁定,而写入文件不仅会产生多线程问题,而且可能会产生毫秒级延迟甚至异常。

      • 日志记录可以从第一行代码开始。消息写入队列,只有文件系统准备好后才会清空

      【讨论】:

      • That is how I did it。尽管您不应该在后台任务上执行此操作,因为它只能在您的应用程序被杀死之前使用 2s of CPU 时间。
      • 阅读您的评论后,我再次浏览了您的代码。我可以猜测这可能是相同的方法。使用事件监听器掩盖了解决方案实际上是多么简单和优雅。我想我的答案可以更容易理解,但在你的答案中也有一些工作代码总是好的。
      • 据我所知,后台线程和普通线程的唯一区别是程序可以在后台线程仍在运行时关闭,而普通线程需要在程序可以关闭。在生产代码中,我可能会确保文件正确关闭并结束线程。您的链接是关于后台任务的,它与后台线程不同。后台线程没有 2 秒的限制。
      • 我只能假设您指的是开发桌面应用程序而不是 Windows 商店应用程序。我建议您阅读文档并实际编写一个 Windows 商店应用程序来测试您的理论。鉴于您没有发布任何代码,我只能假设您的答案只是一个理论。
      猜你喜欢
      • 1970-01-01
      • 2013-10-20
      • 2013-11-05
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      相关资源
      最近更新 更多