2010-04-13 2 views
2

Первый вопрос здесь так привет всем.Как обновить поток/класс GUI из рабочего потока/класса?

Требование, над которым я работаю, - это небольшое тестовое приложение, которое взаимодействует с внешним устройством через последовательный порт. Связь может занять много времени, и устройство может возвращать всевозможные ошибки.

Устройство прекрасно абстрагируется в своем классе, что поток GUI начинает работать в своем потоке и имеет обычные функции открытия/закрытия/чтения данных/записи данных. Графический интерфейс также довольно прост - выберите COM-порт, откройте, закройте, покажите данные чтения или ошибки с устройства, разрешите изменение и запись и т. Д.

Вопрос в том, как обновить GUI из класса устройства? Существует несколько различных типов данных, с которыми связано устройство, поэтому мне нужен относительно общий мост между классом/потоком GUI-класса и классом/потоком рабочего устройства. В графическом интерфейсе к устройству все работает отлично с помощью [Begin] Invoke вызывает открытие/закрытие/чтение/запись и т. Д. В различных событиях, генерируемых графическим интерфейсом.

Я прочитал нить here (How to update GUI from another thread in C#?), где сделано предположение, что графический интерфейс и рабочий поток находятся в одном классе. Поиски Google заставляют создать делегат или как создать классического рабочего, но это совсем не то, что мне нужно, хотя они могут быть частью решения. Итак, есть ли простая, но общая структура, которая может быть использована?

Мой уровень C# умеренный, и я программировал всю свою трудовую жизнь, учитывая ключ. Я выясню это (и отправлю обратно) ... Спасибо заранее за любую помощь.

ответ

6

Вы можете открыть публичный метод в вашем пользовательском интерфейсе, который класс устройства может вызвать в фоновом потоке со всей информацией, необходимой ему для передачи в пользовательский интерфейс. Этот открытый метод будет выполняться в контексте фонового потока, но поскольку он принадлежит классу пользовательского интерфейса, теперь вы можете использовать любой из методов маршалинга вызовов, о которых вы читали.

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

  • добавить метод к классу UI (например MyUIForm) называется что-то вроде UpdateUI(), который принимает любые структуры данных используется для передачи данных из устройства к пользовательскому интерфейсу, который вы используете. Вы можете объявить этот метод в интерфейсе (например, IUIForm), если вы хотите позже поддержать DI/IoC и иметь его реализацию.
  • в потоке A (поток пользовательского интерфейса), ваш пользовательский интерфейс создает класс устройства, инициализирует все необходимые настройки и запускает фоновый поток. Он также передает указатель на себя.
  • на резьбе B устройство собирает данные и звонит MyUIForm.UpdateUI() (или IUIForm.UpdateUI()).
  • UpdateUIInvoke или BeginInvoke в зависимости от ситуации.

Обратите внимание, что имеет преимущество в плане инкапсуляции всех интерфейсов пользовательского интерфейса и представления в ваш пользовательский интерфейс. Теперь ваш класс устройств может сосредоточиться на работе с оборудованием.

Update: Чтобы решить вашу масштабируемость проблемы -

Независимо от того, насколько вашего приложения растет и сколько UI классов у вас есть, вы все еще хотите, чтобы пересечь границу резьбы с помощью BeginInvoke для конкретного класса пользовательского интерфейса вы хотите обновить.(Этот класс пользовательского интерфейса может быть конкретным элементом управления или корнем конкретного визуального дерева, на самом деле это не имеет особого значения). Основная причина заключается в том, что если у вас есть несколько потоков пользовательского интерфейса, вы должны убедиться, что обновление любого пользовательского интерфейса происходит в потоке этот пользовательский интерфейс был создан на основе способа обмена сообщениями Windows и окон. Следовательно, фактическая логика пересечения граничного потока должна быть инкапсулирована в слой пользовательского интерфейса.

Класс устройства не должен заботиться о том, какие классы пользовательского интерфейса и на каком потоке необходимо обновить. Фактически, я лично сделаю устройство полностью неосведомленным о любом пользовательском интерфейсе и просто покажут на нем события, на которые могут подписаться различные классы пользовательского интерфейса.

Обратите внимание, что альтернативное решение состоит в том, чтобы сделать потоки полностью инкапсулированными в классе устройств и сделать UI невежественным о существовании поточной линии. Однако тогда пересечение границы потока становится ответственностью класса устройства и должно содержаться в его логике, поэтому вам не следует использовать способ пользовательского интерфейса для пересечения потоков. Это также означает, что ваш класс устройства привязан к определенному потоку пользовательского интерфейса.

+0

Это, наверное, самый простой способ, и он хорошо работает в начале, и я использовал его раньше, но у него есть тот недостаток, что после того, как графический интерфейс становится больше и состоит из большего количества классов или UserControls (что неизбежно происходит), что он плохо масштабируется. Возможно, я должен был указать в своем вопросе, что причина, по которой я ищу универсальное решение, я полностью ожидаю, что это приложение будет расти со временем и будет хотеть что-то, что масштабируется. – 0xDEADBEEF

0

Это версия с обработчиком событий.
Это упрощено, так что в форме нет элементов управления и нет свойств в классе SerialIoEventArgs.

  1. Поместите код для обновления пользовательского интерфейса под комментарием // Update UI
  2. поместить код для чтения последовательный IO под комментарием // Чтение из последовательного ввода-вывода
  3. Добавить поля/свойства SerialIoEventArgs и запишите его в методе OnReadCompleated.
public class SerialIoForm : Form 
{ 
    private delegate void SerialIoResultHandlerDelegate(object sender, SerialIoEventArgs args); 
    private readonly SerialIoReader _serialIoReader; 
    private readonly SerialIoResultHandlerDelegate _serialIoResultHandler; 

    public SerialIoForm() 
    { 
     Load += SerialIoForm_Load; 
     _serialIoReader = new SerialIoReader(); 
     _serialIoReader.ReadCompleated += SerialIoResultHandler; 
     _serialIoResultHandler = SerialIoResultHandler; 
    } 

    private void SerialIoForm_Load(object sender, EventArgs e) 
    { 
     _serialIoReader.StartReading(); 
    } 
    private void SerialIoResultHandler(object sender, SerialIoEventArgs args) 
    { 
     if (InvokeRequired) 
     { 
      Invoke(_serialIoResultHandler, sender, args); 
      return; 
     } 
     // Update UI 
    } 
} 
public class SerialIoReader 
{ 
    public EventHandler ReadCompleated; 
    public void StartReading() 
    { 
     ThreadPool.QueueUserWorkItem(ReadWorker); 
    } 
    public void ReadWorker(object obj) 
    { 
     // Read from serial IO 

     OnReadCompleated(); 
    } 

    private void OnReadCompleated() 
    { 
     var readCompleated = ReadCompleated; 
     if (readCompleated == null) return; 
     readCompleated(this, new SerialIoEventArgs()); 
    } 
} 

public class SerialIoEventArgs : EventArgs 
{ 
} 
0

Таким образом, после того, как некоторые исследования на основе вышеуказанных ответов, далее Google поиска и просят коллегу, который знает немного о C# мой выбранное решение проблемы ниже. Я по-прежнему интересуюсь комментариями, предложениями и уточнениями.

Прежде всего, мы расскажем о проблеме, которая на самом деле довольно типична в том смысле, что GUI контролирует что-то, что должно оставаться полностью абстрактным, посредством серии событий, ответы на которые GUI должен реагировать. Там есть несколько отдельных проблем:

  1. События сами по себе, с разными типами данных. События будут добавлены, удалены, изменены по мере развития программы.
  2. Как подключить несколько классов, которые составляют GUI (разные UserControls), и классы, которые абстрагируют аппаратное обеспечение.
  3. Все классы могут производить и потреблять события и должны оставаться развязанными, насколько это возможно.
  4. Компилятор должен определить кодирование cockups, насколько это возможно (например, событие, которое посылает один тип данных, но comsumer, что ожидает другой)

Первая часть этого события. Поскольку графический интерфейс и устройство могут создавать несколько событий, возможно, связанных с ними разными типами данных, диспетчер событий удобен.Это должно быть общим в обеих событиях и данных, так:

// Define a type independent class to contain event data 
    public class EventArgs<T> : EventArgs 
    { 
    public EventArgs(T value) 
    { 
     m_value = value; 
    } 

    private T m_value; 

    public T Value 
    { 
     get { return m_value; } 
    } 
} 

// Create a type independent event handler to maintain a list of events. 
public static class EventDispatcher<TEvent> where TEvent : new() 
{ 
    static Dictionary<TEvent, EventHandler> Events = new Dictionary<TEvent, EventHandler>(); 

    // Add a new event to the list of events. 
    static public void CreateEvent(TEvent Event) 
    { 
     Events.Add(Event, new EventHandler((s, e) => 
     { 
      // Insert possible default action here, done every time the event is fired. 
     })); 
    } 

    // Add a subscriber to the given event, the Handler will be called when the event is triggered. 
    static public void Subscribe(TEvent Event, EventHandler Handler) 
    { 
     Events[Event] += Handler; 
    } 

    // Trigger the event. Call all handlers of this event. 
    static public void Fire(TEvent Event, object sender, EventArgs Data) 
    { 
     if (Events[Event] != null) 
      Events[Event](sender, Data); 

    } 
} 

Теперь нам нужны некоторые события и ближайший из мира C, мне нравится перечисления, поэтому я определить некоторые события, что графический интерфейс поднимет:

public enum DEVICE_ACTION_REQUEST 
    { 
    LoadStuffFromXMLFile, 
    StoreStuffToDevice, 
    VerifyStuffOnDevice, 
    etc 
    } 

Теперь в любом месте в пределах объема (пространство имен, как правило) статического класс EventDispatcher, то можно определить новый диспетчер:

 public void Initialize() 
     { 
     foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST))) 
      EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action); 
     } 

Это создает обработчик события для каждого события в ванной гм.

И потребляются, подписавшись на события, как этот код в конструкторе потребляющего объекта Device:

 public DeviceController() 
    { 
     EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) => 
      { 
       InControlThread.Invoke(this,() => 
       { 
        ReadConfigXML(s, (EventArgs<string>)e); 
       }); 
      }); 
    } 

Где InControlThread.Invoke это абстрактный класс, который просто оборачивает вызов Invoke.

Событие может быть поднято GUI просто:

 private void buttonLoad_Click(object sender, EventArgs e) 
     { 
      string Filename = @"c:\test.xml"; 
      EventDispatcher<DEVICE_ACTION_REQUEST>.Fire(DEVICE_ACTION_REQUEST.LoadStuffFromXMLFile, sender, new EventArgs<string>(Filename)); 
     } 

Это имеет то преимущество, что если событие поднимая и потребляющие типов не совпадает (здесь строки файла) компилятор будет ворчать.

Существует множество улучшений, которые могут быть сделаны, но это гайки проблемы. Мне было бы интересно, как я сказал в комментариях, особенно если есть какие-либо вопиющие упущения/ошибки или недостатки. Надеюсь, это поможет кому-то.

+0

Мне кажется, что вы пытаетесь закрепить семантику C на C#. Это не продуктивно. Вы могли бы просто добавить события в класс ComSer и .BeginInvoke (асинхронно), и форма была бы подписана на них и т. Д. Теперь у вас есть эта отвратительная вещь с вашей собственной семантикой, что никто, исходящий из C#, не видит причин. Мне жаль, что это суровое и не обижалось. –

+0

Спасибо за комментарий. Несмотря на то, что вы, по-видимому, можете предоставить обзоры во имя всего мира C#, у меня есть Googled соответствующие пункты из вашего предложения, я все еще не мудрее о том, как они помогут. – 0xDEADBEEF

Смежные вопросы