Я совсем не специалист в шарпе, более того, шарп — не основной мой язык (использую его только для программ, строго требующих GUI) и никак не пойму, как же все-таки делать правильно.
Что требуется: выполнение любых действий не в основном потоке, т.е. полностью исключить подвисание интерфейса.
Программа работает исключительно с сетью, скорости интернета на клиентах разные, многие жалуются.
При этом надо постоянно взаимодействовать с полученными данными и делать это последовательно, отображать изменения в GUI и т.д.
Читал много гугла, везде рекомендуют создавать простыни из callback'ов, писать огромные делегаты на все случаи, использовать сложные конструкции с инвокерами, а в текущем коде, который и без того не очень опрятен, все переделывать очень проблематично.
Не могли бы вы поделиться примерами кода, в котором какие-нибудь типичные синхронные конструкции (например получение данных, парсинг, отправка HTTP-запроса, обновление интерфейса основного окна, опять получение и дальнейший парсинг) сделаны в фоне, а не просто разбиты на функции? Или хотя бы просто поясните, как сделать это с минимальными затратами и максимальной понятностью (т.е. чтобы чтение алгоритма не прерывала простыня взаимодействия с основной формой). Спасибо заранее.
Wpf или WinForms?
Если WPF, то правильное использование MVVM позволяет не париться о Dispatcher.BeginInvoke. Могу пояснить что непонятно.
Если WinForms то я пас.
Для операций в фоне советую использовать асинхронные операции. Если это сеть, то, например BeginSend и BeginWrite. В колбэках прописываем реакцию в GUI. Для того, чтобы операция выполнилась в GUI-потоке используем SynchronizationContext. Вот такой простой пример:
public partial class SimpleForm : Form
{
private readonly SynchronizationContext _context;
private readonly TcpListener _serverListener;
private int _connectionsCount;
public SimpleForm()
{
InitializeComponent();
// Зафиксировали GUI-поток
_context = SynchronizationContext.Current;
// Включаем ожидание входящих соединений
_serverListener = new TcpListener(IPAddress.Loopback, 30000);
_serverListener.Start();
_serverListener.BeginAcceptTcpClient(AcceptCallback, null);
}
private void AcceptCallback(IAsyncResult ar)
{
// Поймали соединение. Завершаем.
var client = _serverListener.EndAcceptTcpClient(ar);
// Сообщаем о новом подключении.
AddConnection();
}
private void AddConnection()
{
// Увеличиваем счетчик подключений
Interlocked.Add(ref _connectionsCount, 1);
// Сообщаем лейблу на форме новое значение. Используем GUI-поток, ибо в Callback'е находимся в чужом потоке из пула.
_context.Post(state => { lbConnCount.Text = _connectionsCount.ToString(); }, null);
}
}
Или хотя бы просто поясните, как сделать это с минимальными затратами и максимальной понятностью
Наверное вопрос аналогичен «какой тип использовать в C++ для строк». В этом отношении C# так разросся, столько вариантов один другого краше — что сложно дать однозначный совет.
Проще всего это, конечно, новая констркция async + await. Но в C#4 нужно доустанавливать AsyncCTP.
Забыл сказать, что проект на .NET 2, там еще нет async и await.
Тогда либо:
1. Создаете новый поток Thread. Форму заполняете посредством вызова Invoke с пред. проверкой InvokeRequired.
2. Асинхронные операции Begin End с тем же вызовом InvokeRequired/Invoke.
Большое спасибо за помощь, плюсанул в карму.
Дал заказчику почитать этот вопрос, смог уговорить на .NET 4 Client Profile и стали использовать вариант, предложенный Illivion.