Как обновить текстовое поле на графическом интерфейсе из другого потока

Я новичок в С#, и я пытаюсь создать приложение для чата на клиентском сервере.

У меня есть RichTextBox в моей форме окон клиента, и я пытаюсь обновить этот элемент управления с сервера, который находится в другом классе. Когда я пытаюсь это сделать, я получаю сообщение об ошибке: "Работа с кросс-потоками недействительна: Control textBox1 доступен из потока, отличного от потока, который был создан на".

Здесь код моей формы Windows:

private Topic topic;  
public RichTextBox textbox1;  
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);

Класс темы:

public class Topic : MarshalByRefObject  
{  
    //Some code
 public  bool addUser(string user, ref RichTextBox textBox1, ref List<string> listBox1)  
 {  
     //here i am trying to update that control and where i get that exception  
     textBox1.Text += "Connected to server... \n";  
}

Итак, как это сделать? Как я могу обновить элемент управления текстовым полем из другого потока?


Я пытаюсь создать базовое клиентское/серверное приложение чата, используя удаленную сеть .net. Я хочу, чтобы окна создавали клиентское приложение и консольное серверное приложение как отдельные .exe файлы. Здесь im пытается вызвать функцию сервера AddUser от клиента, и я хочу, чтобы функция AddUser обновила мой графический интерфейс. Ive модифицированный код, как вы предложили Jon, но теперь вместо исключения с перекрестными потоками у меня есть это исключение... "SerializationException: Type Topic в Assembly не помечен как сериализуемый".

Положите весь мой код ниже, постарайтесь максимально упростить его.
Любое предложение приветствуется. Большое спасибо.

Сервер:

  namespace Test
{
    [Serializable]
    public class Topic : MarshalByRefObject
    {
        public bool AddUser(string user, RichTextBox textBox1, List<string> listBox1)
        {
            //Send to message only to the client connected
            MethodInvoker action = delegate { textBox1.Text += "Connected to server... \n"; };
            textBox1.BeginInvoke(action);
            //...
            return true;
        }

        public class TheServer
        {
            public static void Main()
            {

                int listeningChannel = 1099;

                BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
                srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;

                BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();

                IDictionary props = new Hashtable();
                props["port"] = listeningChannel;

                HttpChannel channel = new HttpChannel(props, clntFormatter, srvFormatter);
                // Register the channel with the runtime            
                ChannelServices.RegisterChannel(channel, false);
                // Expose the Calculator Object from this Server
                RemotingConfiguration.RegisterWellKnownServiceType(typeof(Topic),
                                                    "Topic.soap",
                                                    WellKnownObjectMode.Singleton);
                // Keep the Server running until the user presses enter
                Console.WriteLine("The Topic Server is up and running on port {0}", listeningChannel);
                Console.WriteLine("Press enter to stop the server...");
                Console.ReadLine();
            }
        }
    }

}  

Клиент формы Windows:

// Create and register a channel to communicate to the server
        // The Client will use the port passed in as args to listen for callbacks

        BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
        srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;
        BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();
        IDictionary props = new Hashtable();
        props["port"] = 0;

        channel = new HttpChannel(props, clntFormatter, srvFormatter);
        //channel = new HttpChannel(listeningChannel);

        ChannelServices.RegisterChannel(channel, false);
        // Create an instance on the remote server and call a method remotely
        topic = (Topic)Activator.GetObject(typeof(Topic), // type to create
        "http://localhost:1099/Topic.soap" // URI
        );


        private Topic topic;
        public RichTextBox textbox1;
        bool check = topic.addUser(textBoxNickname.Text,textBox1, listitems);

Ответ 1

Вам нужно либо использовать BackgroundWorker, либо Control. Invoke/BeginInvoke. Анонимные функции - анонимные методы (С# 2.0) или лямбда-выражения (С# 3.0) делают это проще, чем раньше.

В вашем случае вы можете изменить свой код на:

public bool AddUser(string user, RichTextBox textBox1, List listBox1)
{
    MethodInvoker action = delegate
         { textBox1.Text += "Connected to server... \n"; };
    textBox1.BeginInvoke(action);
}

Несколько замечаний:

  • Чтобы соответствовать соглашениям .NET, это следует называть AddUser
  • Вам не нужно передавать текстовое поле или список по ссылке. Я подозреваю, что вы не совсем понимаете, что означает ref - см. мою статью о передаче параметров для более подробной информации.
  • Разница между Invoke и BeginInvoke заключается в том, что BeginInvoke не будет ждать, пока делегат будет вызван в потоке пользовательского интерфейса до его продолжения - поэтому AddUser может вернуться до фактического обновления текстового поля. Если вы не хотите этого асинхронного поведения, используйте Invoke.
  • Во многих образцах (включая некоторые из моих!) вы найдете людей, использующих Control.InvokeRequired, чтобы узнать, нужно ли им звонить Invoke/BeginInvoke. В большинстве случаев это на самом деле избыточно - нет никакого реального вреда при вызове Invoke/BeginInvoke, даже если вам это не нужно, и часто обработчик будет когда-либо вызываться из нити, отличной от UI. Опускание проверки делает код более простым.
  • Вы также можете использовать BackgroundWorker, как я упоминал ранее; это особенно подходит для индикаторов выполнения и т.д., но в этом случае, возможно, так же легко сохранить текущую модель.

Для получения дополнительной информации об этой и других тематических разделах см. мой учебник по потокам или Джо Альбахари один.

Ответ 2

Использовать метод Invoke

// Updates the textbox text.
private void UpdateText(string text)
{
  // Set the textbox text.
  yourTextBox.Text = text;
}

Теперь создайте делегат, который имеет ту же подпись, что и ранее определенный метод:

public delegate void UpdateTextCallback(string text);

В вашем потоке вы можете вызвать метод Invoke на вашем TextBox, передать делегат для вызова, а также параметры.

yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText), 
            new object[]{"Text generated on non-UI thread."});