日志类事件异常:主窗体外部调用时事件未被接收
问题:Controller调用Logging.Send时主窗体无法接收日志消息
我创建了一个Logging类,其Send方法会触发Log事件,将字符串message传递给所有订阅类。主窗体Form1订阅该事件后,会根据ELogType类型将消息追加到RichTextBox中作为日志显示。
问题在于:仅在主窗体内部调用Logging.Send时日志能正常显示,在Controller类中调用该方法时,事件虽触发但主窗体未接收到消息。
问题示例代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public enum ELogType { Primary, Simple, Extra, Readout, Debug } public class Logging { public Logging() { } public event EventHandler<LoggerMessageArgs> Log; public bool PostPrimaryLogs = true, PostSimpleLogs = true, PostExtraLogs = false, PostReadoutLogs = false, PostDebugLogs = false; public void Send(string message) { Send(message, ELogType.Primary); } public void Send(string primaryMessage, string simpleMessage) { Send(new[] { primaryMessage, simpleMessage }, new[] { ELogType.Primary, ELogType.Simple }); } public void Send(string message, ELogType type) { Console.WriteLine("sendtest"); //to check that the send event was happening. if ((type == ELogType.Primary && PostPrimaryLogs == true) //is there some way to avoid this giant if statement here? || (type == ELogType.Simple && PostSimpleLogs == true) || (type == ELogType.Extra && PostExtraLogs == true) || (type == ELogType.Readout && PostReadoutLogs == true) || (type == ELogType.Debug && PostDebugLogs == true)) Log?.Invoke(this, new LoggerMessageArgs(message, type)); } public void Send(string[] messages, ELogType[] types) { if (messages.Length == types.Length) for (int i = 0; i < messages.Length; i++) { Send(messages[i], types[i]); } } } public class LoggerMessageArgs { public string Message { get; } public ELogType LogType { get; } public LoggerMessageArgs(string message) { Message = message; LogType = ELogType.Primary; } public LoggerMessageArgs(string message, ELogType logType) { Message = message; LogType = logType; } } public class Controller { Logging Logger = new Logging(); public async Task DoControl() { Logger.Send("beginning doControl", ELogType.Primary); await Task.Delay(1000); //do work Logger.Send("finished doControl", ELogType.Primary); return Task.CompletedTask; } } public partial class Form1: Form { Logging Logger = new Logging(); //needs an additional RichTextBox LogTextBox, and a RichTextBox SimpleLogTextBox. The simple log box isn't super important to the issue at hand. It's mostly used as a status indicator. Form1() { InitializeComponent(); Logger.Log += (sender, e) => { Action action = () => { if (e.LogType == ELogType.Primary || e.LogType == ELogType.Debug || e.LogType == ELogType.Extra || e.LogType == ELogType.Readout) { LogTextBox.AppendText($@"{DateTime.Now:hh\:mm\:ss\.ffff}: {e.Message}{Environment.NewLine}"); LogTextBox.SelectionStart = LogTextBox.Text.Length; LogTextBox.ScrollToCaret(); } if (e.LogType == ELogType.Simple) { SimpleLogTextBox.AppendText($@"{e.Message}{Environment.NewLine}"); SimpleLogTextBox.SelectionStart = SimpleLogTextBox.Text.Length; SimpleLogTextBox.ScrollToCaret(); } }; if (LogTextBox.InvokeRequired) { LogTextBox.Invoke(action); } else { action(); } }; } private void TestButton_Click(object sender, EventArgs e) { Logger.Send(new string[] { "Form Primary Log Test", "Form Simple Log Test" }, new ELogType[] { ELogType.Primary, ELogType.Simple }); //logs come back from this one Controller.DoControl(); //no logs come back from this one. } }
问题原因
Form1和Controller各自实例化了独立的Logging对象,Form1仅订阅了自身Logger实例的Log事件,而Controller调用的是自己Logger实例的Send方法,两个实例的事件系统完全隔离,因此主窗体接收不到Controller触发的日志事件。
解决方案
方式一:通过构造函数注入共享Logging实例
让Controller接收外部传入的Logging实例,而非自行创建,确保Form1和Controller使用同一个Logger对象。
- 修改Controller类:
public class Controller { private readonly Logging _logger; // 通过构造函数接收Logging实例 public Controller(Logging logger) { _logger = logger; } public async Task DoControl() { _logger.Send("beginning doControl", ELogType.Primary); await Task.Delay(1000); // 模拟业务逻辑 _logger.Send("finished doControl", ELogType.Primary); } }
- 修改Form1的TestButton_Click方法:
private void TestButton_Click(object sender, EventArgs e) { Logger.Send(new string[] { "Form Primary Log Test", "Form Simple Log Test" }, new ELogType[] { ELogType.Primary, ELogType.Simple }); // 创建Controller时传入Form1的Logger实例 var controller = new Controller(Logger); // 异步方法调用:使用_ = 避免编译器警告,若需等待完成可改为 await controller.DoControl()(需将方法改为async void) _ = controller.DoControl(); }
方式二:将Logging设计为单例模式
让Logging类全局只有一个实例,Form1和Controller直接使用该单例,确保事件订阅与触发关联。
- 修改Logging类:
public class Logging { // 私有静态实例,确保全局唯一 private static readonly Logging _instance = new Logging(); // 公开静态属性,供外部获取实例 public static Logging Instance => _instance; // 私有构造函数,禁止外部实例化 private Logging() { } // 其余原有代码保持不变... public event EventHandler<LoggerMessageArgs> Log; public bool PostPrimaryLogs = true, PostSimpleLogs = true, PostExtraLogs = false, PostReadoutLogs = false, PostDebugLogs = false; // ... 所有Send方法代码不变 }
- 修改Form1中的Logger引用:
public partial class Form1: Form { // 使用Logging单例实例 private readonly Logging _logger = Logging.Instance; Form1() { InitializeComponent(); _logger.Log += (sender, e) => { // 原有事件处理逻辑不变... }; } private void TestButton_Click(object sender, EventArgs e) { _logger.Send(new string[] { "Form Primary Log Test", "Form Simple Log Test" }, new ELogType[] { ELogType.Primary, ELogType.Simple }); var controller = new Controller(); _ = controller.DoControl(); } }
- 修改Controller中的Logger引用:
public class Controller { // 使用Logging单例实例 private readonly Logging _logger = Logging.Instance; public async Task DoControl() { _logger.Send("beginning doControl", ELogType.Primary); await Task.Delay(1000); // 模拟业务逻辑 _logger.Send("finished doControl", ELogType.Primary); } }
额外注意点
- 异步方法调用:原代码中直接调用
Controller.DoControl()会导致未等待的异步操作,虽然不影响日志显示,但可能引发潜在的线程问题。建议使用_ = controller.DoControl()(忽略返回任务),若需等待操作完成,可将TestButton_Click改为async void并使用await controller.DoControl()。 - 代码优化:原Logging类中的日志类型判断可以通过字典映射简化,比如用
Dictionary<ELogType, bool>存储日志开关,避免冗长的if语句。
内容的提问来源于stack exchange,提问作者Cameron Hadland
相关产品推荐
相关产品推荐

