Why can't method 1 be executed correctly, through Action <'string'> updateMsgAction, to add a message to the ListBox, but method 2 can be executed correctly?
I tried using method 3 BeginInvoke(Delegate, Object[])
, the compiler displays an error.
I want that method #2 can be like method #1 become a method to call when need update message.
Method 1: UpdateMsg
using 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);
}
});
}
Method 3: using BeginInvoke(Delegate, Object[])
public delegate void MyDelegate(ListBox myControl, string myMessage);
public void DelegateMethod(ListBox myControl, string myMsg)
{
myControl.Items.Add(myMsg);
}
MQTT Server Start:
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));
}
CodePudding user response:
BeginInvoke is used to execute some code in the thread in which the control was created. It's mandatory update controls in their own thread (usually in main thread) or you get an exception. So, your use or BeginInvoke is correct.
The problem is that, when you are running in the main thread and are be able to update the control, you delegate in an action the update. The action run in other thread and you are "cancelling" de BeginInvoke and getting the expected exception trying to update a control in other thread.
I use SynchronizationContext for this kind of things. In your form's code, add a variable:
private static SynchronizationContext Context;
UPDATE: And initialize in the constructor:
public YourForm()
{
this.InitializeComponent();
Context = SynchronizationContext.Current;
// Other code
}
Add this method:
private static void RunInMainThread(Action operation)
{
if (Context != SynchronizationContext.Current)
{
Context.Post(o => operation(), null);
}
else
{
operation();
}
}
If you already are running in main thread, your code run inmediatly. In other case, Post action run asynchronously in the main thread. You can use Send instead or Post to run synchronously. And use it when you need access to controls:
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...
}
}
You can create an extension methods (Post and Send) for SynchronizationContext instead of RunInMainThread and reuse in your projects.