C# Winforms Control.BeginInvoke 和 Action<T>

C# Winforms Control.BeginInvoke with Action<T>

为什么方法一不能正确执行,通过Action<'string'>updateMsgAction,给ListBox添加一条消息,方法二却能正确执行?

我尝试使用方法 3 BeginInvoke(Delegate, Object[]) , 编译器显示错误。

我希望方法#2 可以像方法#1 一样成为需要更新消息时调用的方法。

方法 1UpdateMsg 使用 Action<string>

    private Action<string> updateMsgAction;

    public void UpdateMsg()
    {
        updateMsgAction = new Action<string>( (s) =>
        {
            MsgList.Items.Add(s);

            if (MsgList.Items.Count > 1000)
            {
                MsgList.Items.RemoveAt(0);
            }
        });
    }

方法三:使用BeginInvoke(Delegate, Object[])

    public delegate void MyDelegate(ListBox myControl, string myMessage);

    public void DelegateMethod(ListBox myControl, string myMsg)
    {
        myControl.Items.Add(myMsg);
    }

MQTT 服务器启动:

    public async void StartMqttServer()
    {
        try
        {
            var mqttFactory = new MqttFactory();

            if (mqttServer == null)
            {
                var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().WithDefaultEndpointPort(int.Parse(txtBoxPort.Text)).Build();
                mqttServer.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(OnMqttServerClientConnected);
                mqttServer.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(OnMqttServerClientDisconnected);
                mqttServer.ClientSubscribedTopicHandler = new MqttServerClientSubscribedTopicHandlerDelegate(OnMqttServerCleitnSubScribedTopic);
                mqttServer.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(OnMqttServerCleitnUnsubScribedTopic);
                mqttServer.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(OnMqttServerApplicationMessageReceived);
                await mqttServer.StartAsync(mqttServerOptions);

                MsgList.BeginInvoke(updateMsgAction, "MQTT Server is started."); //Method 1

                /*MsgList.BeginInvoke(new Action<string>((s) =>
                { MsgList.Items.Add(s); }), "MQTT Server is started."));*/ //Method 2

                //MsgList.BeginInvoke(new MyDelegate(DelegateMethod), "MQTT Server is started.")); //Method 3
            }
        }
        catch(Exception ex)
        {
            MsgList.BeginInvoke(updateMsgAction, "MQTT Server start fail."));
        }
    }

OnMqttServerApplicationMessageReceived:

    public void OnMqttServerApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
    {
     // Method 1
     MsgList.BeginInvoke(updateMsgAction, 
       String.Format("Client[{0}]>> Topic:{1} Payload:{2} Qos:{3} Retain:{4}",
          e.ClientId, e.ApplicationMessage.Topic, e.ApplicationMessage.Payload.ToString(),
          e.ApplicationMessage.QualityOfServiceLevel, e.ApplicationMessage.Retain));
    }

BeginInvoke 用于在创建控件的线程中执行一些代码。它是他们自己的线程(通常在主线程中)中的强制更新控件,否则您会遇到异常。因此,您的使用或 BeginInvoke 是正确的。

问题是,当您 运行 在主线程中并且能够更新控件时,您将更新委托给一个动作。其他线程中的操作 运行 并且您正在“取消”de BeginInvoke 并在尝试更新其他线程中的控件时出现预期的异常。

我对这类事情使用 SynchronizationContext。在您的表单代码中,添加一个变量:

private static SynchronizationContext Context;

更新:并在构造函数中初始化:

    public YourForm()
    {
        this.InitializeComponent();

        Context = SynchronizationContext.Current;

        // Other code
    }

添加此方法:

private static void RunInMainThread(Action operation)
    {
        if (Context != SynchronizationContext.Current)
        {
            Context.Post(o => operation(), null);
        }
        else
        {
            operation();
        }
    }

如果您已经 运行 进入主线程,您的代码 运行 立即生效。在其他情况下,Post 在主线程中异步操作 运行。您可以改为使用 Send 或 Post 到 运行 同步。并在需要访问控件时使用它:

public void OnMqttServerApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
{    
   var msg = string.Format(
       "Client[{0}]>> Topic:{1} Payload:{2} Qos:{3} Retain:{4}",
       e.ClientId, 
       e.ApplicationMessage.Topic, 
       e.ApplicationMessage.Payload.ToString(),
       e.ApplicationMessage.QualityOfServiceLevel, 
       e.ApplicationMessage.Retain);

    RunInMainThread(() => 
    {
        MsgList.Items.Add(msg);
        // Other code...
    }
}

您可以为 SynchronizationContext 而不是 RunInMainThread 创建扩展方法(Post 和 Send)并在您的项目中重用。