You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

日志类事件异常:主窗体外部调用时事件未被接收

问题: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对象。

  1. 修改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);
    }
}
  1. 修改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直接使用该单例,确保事件订阅与触发关联。

  1. 修改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方法代码不变
}
  1. 修改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();
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:59:53