BeginInvoke из другого потока сильно тормозит и вешает намертво ГУИ

300
26 декабря 2017, 14:23

Есть сторонний класс TelnetClient. С помощью него я по нажатию кнопки устанавливаю соединение, и подписываюсь на входящие сообщения. По нажатию другой кнопки отправляю команду на чтение удаленного каталога. Исходники стороннего класса доступны, он основан на TPL, и HandleMessageReceived вызывается не из ГУИ-потока, поэтому я вынужден использовать (Begin)Invoke

TelnetClient tc = null;
private async void buttonLogin_Click(object sender, EventArgs e)
{
    tc = new TelnetClient("host", 23, TimeSpan.FromSeconds(3), 
        new System.Threading.CancellationToken());            
    tc.MessageReceived += HandleMessageReceived;       
    await tc.Connect();
    await tc.Send("username");
    await tc.Send("password");
}
private void HandleMessageReceived(object sender, MessageEventArgs mea)
{        
    this.BeginInvoke(new Action(() =>
        {
            textBox1.Text += mea.Message;                
        }));
}     
private async void buttonSend_Click(object sender, EventArgs e)
{
    await tc?.Send("dir");
}

Код работает, но плохо:

1) Выполняется излишне долго (несколько минут). Любым нормальным telnet приложением на этом же хосте команда dir выполняется в разы быстрее (около секунды).

2) Блокирует ГУИ.

Вопросы

1) Почему блокируется ГУИ? Как избежать?

2) С чем может быть связана низкая производительность? Как избежать?

Answer 1

20K вызовов — это очень много. UI просто не успевает принимать ваши сообщения.

Делайте по-другому. Если у вашего TelnetClient есть API со Stream'ом (как в TcpClient'е), используйте его и читайте построчно.

Если нет — буферизуйте строки в HandleMessageReceived до какого-нибудь приличного размера, и отправляйте в UI-при достижении предела или через какое-то разумное время (не меньше десятой доли секунды).

Если вы случайно пользуетесь Reactive Extension'ами, то это великолепная цель для их применения (управление backpressure):

Observable.FromEventPattern<MessageReceived>(tc, "MessageReceived")
          .Select(mea => mea.Message)
          .Buffer(TimeSpan.FromSeconds(0.1))
          .Where(list => list.Count > 0)
          .Select(list => string.Concat(list))
          .ObserveOn(mainSynchronizationContext)
          .Subscribe(s => textBox1.Text += s);
Answer 2

А если вы в файл писать будете, то тоже так делать? А если при этом потоков много сразу?
"Процесс не может получить..." - придется выучить наизусть ^^

Надо сделать очередь с обрабатывающим ее одним отдельным потоком writer, соблюдающим заданный интервал, чтобы и обновлялось достаточно быстро и GUI не вешался.
И эту архитектуру копировать из проекта в проект. Даже если пока вроде бы и нен адо.
Я ее делаю сам, но можно поискать что-то готовое, выше вот заикнулись про Reactive.

READ ALSO
Вывод информации из класса во время выполнения метода в TextBox формы

Вывод информации из класса во время выполнения метода в TextBox формы

Добрый день, столкнулся с непониманием того как вывести актуальную информацию на TextBox из метода классаУ меня есть класс :

218
Чтение события из другой программы

Чтение события из другой программы

Есть 2 программы1 работает в фоне(далее ФП) другая в оконном режиме(далее ОП)

280
Как десериализовать ArraySegment&lt;byte&gt; в object внутри WebSocke

Как десериализовать ArraySegment<byte> в object внутри WebSocke

Как десериализовать ArraySegment в object внутри WebSocke? Я закоментировал часть кода в которой пытался это сделать, к примеру я пытался использовать...

242
Получить дочерний элемент UI в Unity?

Получить дочерний элемент UI в Unity?

Как у canvas в unity через скрипт получить его дочерний button, чтобы потом иметь возможность обращаться к нему и проверять на нажатия ? Пробовал так:

263