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

如何在属性变更时更新Spectre.Console的LiveDisplay?

问题描述

我正在开发基于COM连接的设备控制器,包含监听、写入、状态查询三个线程,计划用Spectre.Console的LiveDisplay实时输出各线程状态(运行/暂停)及测试计时器。

目前遇到的核心问题:线程状态属性变更时无法更新控制台——更新需要每次状态变化调用ctx.Refresh(),但找不到在AnsiConsole.Start()方法外部调用该方法的途径。

我采用了一个实现INotifyPropertyChanged的独立类存储状态,线程向其传递值,但不确定这个设计是否合理,后续考虑将其与控制器类合并。

现有代码

ControllerData(状态存储类)

internal class ControllerData : INotifyPropertyChanged
{
    private string _writeThreadState = "";
    private string _readThreadState = "";
    private string _stateThreadState = "";
    private string _actionMessage = "";

    public String WriteThreadState
    {
        get => _writeThreadState;
        set
        {
            _writeThreadState = value;
            OnPropertyChanged(nameof(WriteThreadState));
        }
    }
    public String ReadThreadState
    {
        get => _readThreadState;
        set
        {
            _readThreadState = value;
            OnPropertyChanged(nameof(ReadThreadState));
        }
    }
    public String StateThreadState
    {
        get => _stateThreadState;
        set
        {
            _stateThreadState = value;
            OnPropertyChanged(nameof(StateThreadState));
        }
    }
    public String ActionMessage
    {
        get => _actionMessage;
        set
        {
            _actionMessage = value;
            OnPropertyChanged(nameof(ActionMessage));
        }
    }
    public StringBuilder Output { get; set; } = new StringBuilder();

    public event PropertyChangedEventHandler? PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Logger类(控制台输出类)

internal class Logger
{
    private ControllerData data;

    public Logger(Controller cont)
    {
        data = cont.loggingData;
        data.PropertyChanged += ControllerDataPropertyChangedHandler;
    }

    private void ControllerDataPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        //refresh console
    }

    public void Start()
    {
        Table table = new Table()
            .Border(TableBorder.Rounded)
            .AddColumn("Thread")
            .AddColumn("State");

        table.AddRow("Listen Thread", data.ReadThreadState);
        table.AddRow("Write Thread", data.WriteThreadState);
        table.AddRow("Check state Thread", data.StateThreadState);

         AnsiConsole.Live(table)
            .Start(ctx =>
            {
                ctx.Refresh();
            });
    }
}

主函数

static void Main(string[] args)
{
    ComConnector connector = new ComConnector();

    try
    {
        Controller cont = new Controller(connector);
        Logger log = new Logger(cont);
        log.Start();
        cont.StartMessaging(20000);
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
        throw;
    }
}
解决方案

设计合理性说明

你当前用独立类存储状态的设计是合理的,符合单一职责原则——状态管理和业务逻辑分离,后续合并到控制器类也完全可行,不影响当前问题的修复。

核心修改:保存LiveContext并绑定状态变更

要解决ctx.Refresh()无法外部调用的问题,只需在AnsiConsole.Live().Start()内部把LiveDisplayContext实例保存下来,让PropertyChanged事件可以直接调用它。同时要注意线程安全,因为状态变更来自不同的工作线程。

修改后的Logger类

internal class Logger
{
    private ControllerData _data;
    private LiveDisplayContext? _liveContext;
    private readonly Table _table;
    private readonly Controller _controller;

    public Logger(Controller cont)
    {
        _controller = cont;
        _data = cont.loggingData;
        _data.PropertyChanged += ControllerDataPropertyChangedHandler;

        // 提前初始化表格,避免重复创建
        _table = new Table()
            .Border(TableBorder.Rounded)
            .AddColumn("Thread")
            .AddColumn("State");
    }

    private void ControllerDataPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        // 根据变更的属性更新对应表格单元格
        switch (e.PropertyName)
        {
            case nameof(ControllerData.ReadThreadState):
                _table.UpdateCell(0, 1, _data.ReadThreadState);
                break;
            case nameof(ControllerData.WriteThreadState):
                _table.UpdateCell(1, 1, _data.WriteThreadState);
                break;
            case nameof(ControllerData.StateThreadState):
                _table.UpdateCell(2, 1, _data.StateThreadState);
                break;
        }

        // 确保在LiveDisplay的线程执行刷新,避免跨线程问题
        _liveContext?.Post(ctx => ((LiveDisplayContext)ctx!).Refresh(), _liveContext);
    }

    public void Start()
    {
        // 初始化表格初始状态
        _table.AddRow("Listen Thread", _data.ReadThreadState);
        _table.AddRow("Write Thread", _data.WriteThreadState);
        _table.AddRow("Check state Thread", _data.StateThreadState);

        AnsiConsole.Live(_table)
            .Start(ctx =>
            {
                _liveContext = ctx;
                // 阻塞等待控制器线程完成,否则LiveDisplay会立即退出
                _controller.WaitForThreadsCompletion();
            });
    }
}

配套修改(Controller类)

需要在Controller中添加等待线程完成的方法,避免LiveDisplay启动后立即退出:

internal class Controller
{
    public ControllerData loggingData { get; } = new ControllerData();
    private readonly List<Thread> _workerThreads = new();

    public void StartMessaging(int duration)
    {
        // 示例:启动三个线程
        _workerThreads.Add(new Thread(ListenThread) { Name = "Listen" });
        _workerThreads.Add(new Thread(WriteThread) { Name = "Write" });
        _workerThreads.Add(new Thread(StateCheckThread) { Name = "StateCheck" });

        _workerThreads.ForEach(t => t.Start());
    }

    // 添加此方法,等待所有工作线程结束
    public void WaitForThreadsCompletion()
    {
        foreach (var thread in _workerThreads)
        {
            thread.Join();
        }
    }

    // 省略原有线程实现...
    private void ListenThread() { /* ... */ }
    private void WriteThread() { /* ... */ }
    private void StateCheckThread() { /* ... */ }
}

关键注意点

  1. 线程安全:使用LiveDisplayContext.Post()方法,确保刷新操作在LiveDisplay的线程执行,避免跨线程操作异常。
  2. LiveDisplay生命周期:AnsiConsole.Live().Start()的委托会阻塞到内部逻辑完成,所以必须等待控制器的工作线程结束,否则LiveDisplay会直接关闭。
  3. 表格更新效率:用UpdateCell()直接修改对应单元格,比重新创建表格更高效。

内容的提问来源于stack exchange,提问作者N A

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:01:03