I'm making my way through threading design patterns and am stuck trying to resolve a small practice project. I get a [InvalidOperationException: 'Cross-thread operation not valid: Control 'txtEventLogger' accessed from a thread other than the thread it was created on.'] when an event in my UI triggered by my object in another thread, calls a method in my UI from the UI event handler, to log some events in a textbox. How can I make this thread safe? Or am I using the wrong design pattern for handling object events when it comes to using threads?
My GUI WinForm has 3 TextBoxes and 1 button:
- txtAmountOfMessages
- txtMessageToSend
- txtEventLogger
- btnStart
I have a class named Relay, and another class derived of EventArgs CustomEventArgs for event handling.
public class Relay
{
    //CustomEventArgs, derived EventArgs class to hold object index and a string message
    public delegate void OnSend(object sender, CustomEventArgs e);
    public event OnSend Sent;
    public int index;
    public int Index {get => index; set => index = value; }
        
    public void Send(string UserMessage)
    {
        //TODO: Meanwhile trigger event for testing
        //EventArgs class not shown, but it takes:
        //CustomEventArgs Constructor  CustomEventArgs(int index, string message)
        Sent(this, new CustomEventArgs(this.index, UserMassage.Length.ToString()));
    }
}
My WinForm code:
public partial class Form1 : Form
{
    private void btnStart_Click(object sender, EventArgs e)
    {
        // create a couple threads
        for (int i = 1; i < 3; i++)
        {                                               // txtAmountOfMessages = 3
            new Thread(() => CreateRelaysAndSendMessage(Convert.ToInt32(txtAmountOfMessages.Text), txtMessageToSend.Text)).Start();
        }
    }
    private void CreateRelaysAndSendMessage(int AmountOfMessages, string Message)
    {
        List<Relay> relayList = new List<Relay>();
        // Create 5 objects of Relay class, index, and subscribe to ui eventhandler
        for (int i = 0; i <= 4; i++)
        {
            relayList.Add(new Relay());
            relayList[i].Index = i;
            relayList[i].Sent += RelaySent;
        }
        // For each object, call .Send, 3 times (from txtAmountOfMessages value)
        foreach(Relay myRelay in relayList)
        {
            for (int j = 1; j <= AmountOfMessages; j++)
            {
                myRelay.Send(Message);
            }
        }
    }
    private void RelaySent(object sender, CustomEventArgs e)
    {
        // Exception handling error here
        Log("Relay number " + e.Index.ToString() + " sent msg " + e.Message);            
    }
    public void Log(string Message)
    {
        // Exception handling error here
        //System.InvalidOperationException: 'Cross-thread operation not valid: Control 'txtEventLogger' accessed from a thread other than the thread it was created on.'
        txtEventLogger.AppendText(Message + Environment.NewLine);
    }
}
 
    