На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: maxim84_
  
> Разнопоточный доступ к GUI , C# samples
    По следам недавних вопросов на форуме, и в связи с неосвещенностью вопроса в FAQ родилась эта небольшая статья:
    Создан тестовый проект (VS2005) со следующим функционалом:
    - при нажатии на кнопку Start создается отдельный поток, который в цикле записывает данные в базу данных (в данном примере использовался Access).
    - после каждого добавления записи, при успешности операции - двигается прогресс бар для индикации процесса заливки записей.
    - после завершения заливки данных разблокируется кнопка Start для возможности повторить еще раз процесс заливки данных.
    Код обработчика кнопки старта процесса:
    ExpandedWrap disabled
      private void button1_Click(object sender, EventArgs e)
              {
                  //Control.CheckForIllegalCrossThreadCalls = false;
                  button1.Enabled = false;
                  Thread th = new Thread(new ParameterizedThreadStart(DoGenerateRecords));
                  th.Start(connectionString);
              }
    Я специально использовал ParameterizedThreadStart для того, чтобы показать, как создаются потоки с передачей параметров.
    Код рабочего метода потока:
    ExpandedWrap disabled
      /// <summary>
              /// process generate records to DB
              /// </summary>
              private void DoGenerateRecords(object param)
              {
                  OleDbConnection conn = new OleDbConnection((string)param);
                  OleDbCommand cmd = new OleDbCommand("ADD_UNIT", conn);
                  cmd.CommandType = CommandType.StoredProcedure;
                  cmd.Parameters.Add("@name", OleDbType.Char, 50);
                  cmd.Parameters.Add("@note", OleDbType.Char, 50);
                  int realCount = 0;
                  try
                  {
                      conn.Open();
                      for (int i = 0; i < COUNT; i++)
                      {
                          cmd.Parameters["@name"].Value = "some name " + i.ToString();
                          cmd.Parameters["@note"].Value = "some note " + i.ToString();
                          if (cmd.ExecuteNonQuery() > 0)
                          {
                              realCount++;
                              //не правильный доступ.
                              //progressBar1.Value ++;
       
                              //правильный доступ
                              if (onProgress != null) onProgress(realCount);
                          }
                      }
                  }
                  catch (Exception ex)
                  {
                      MessageBox.Show(ex.Message);
                  }
                  finally
                  {
                      if (conn.State == ConnectionState.Open) conn.Close();
                      if (onEndProcess != null) onEndProcess();
                  }
              }
    Если раскоментировать строку progressBar1.Value ++; в рабочем методе потока, приложение заработает, проблем не будет, но: если только попробовать запустить Debug, мы получим эксепшн:
    Cross thread operation not valid:....., это среда разработки предупреждает о том, что возможны ошибки при доступе к GUI из другого потока. Для ленивых программистов - эту генерацию исключений можно отключить -
    ExpandedWrap disabled
      Control.CheckForIllegalCrossThreadCalls = false;

    , но как говорил Винни-Пух, "Нужно делать так как нужно, а так как не нужно делать не нужно" :)
    Дальше:
    Для добавления записей использовался хранимый запрос ADD_UNIT (синтаксис можно посмотреть в аттаче).
    Для сигнализации окончания процесса и прогресса использовались делегаты:
    ExpandedWrap disabled
      delegate void AddProgressEventHandler(int val);
      delegate void EndPocessEventHandler();
      //
       private event AddProgressEventHandler onProgress;
       private event EndPocessEventHandler onEndProcess;

    И собственно сам код подписчиков событий:
    ExpandedWrap disabled
      void Form1_onEndProcess()
              {
                  if (button1.InvokeRequired)
                  {
                      this.BeginInvoke(
                          new EndPocessEventHandler(Form1_onEndProcess));
                  }
                  else button1.Enabled = true;
              }
       
              void Form1_onProgress(int val)
              {
                  if (progressBar1.InvokeRequired)
                  {
                      this.BeginInvoke(
                          new AddProgressEventHandler(Form1_onProgress),
                          new object[] { val });
                  }
                  else progressBar1.Value = val;
              }

    свойство InvokeRequired элемента управления возвращает true, если достут к элементу управления осуществляется с другого потока. Методы Invoke/BeginInvoke формы позволяют выполнить метод в потоке GUI формы. При этом Invoke - синхронно, BeginInvoke - ассинхронно.
    Все, разрулили, теперь и студия не бросается исключениями, и на сердце спокойней :)
    Спасибо тем, кто дочитал до конца, в аттаче - рабочий проект (VS2005). Жду критики и коментариев.
    Прикреплённый файлПрикреплённый файлGUI_MultithreadTest.zip (43.64 Кбайт, скачиваний: 656)
      Пришло письмо от робота, что архив битый, проверил, все нормально, но на всякий случай выкладываю еще раз:
      Прикреплённый файлПрикреплённый файлGUI_MultithreadTest.zip (43.64 Кбайт, скачиваний: 668)
        Некорректность(нельзя не отметить, очень типичная): После BeginInvoke должен идти EndInvoke, обязательно. Архивы(оба) действительно битые, по крайней мере в explorer(vista) не открываются
          Если архив распаковать WinRar'ом, то там лежит один файл с неизвестным расширением.
            Просто внутри архива лежит еще один архив. Так добавьте к этому файлу расширение .zip и будет тот самый архив.
              Цитата ANDLL @
              После BeginInvoke должен идти EndInvoke, обязательно.
              - Нет, не обязательно, читаем Дж. Рихтера CLR via С# 2005 - стр. 604, - для GUI - это единственное исключение из правил, когда EndInvoke не обязателен
                Предвосхищая аналогичные вопросы по WPF - там Invoke можно найти в объекте типа System.Windows.Threading.Dispatcher. Ссылку на объект этого типа держит само приложение (System.Windows.Application.Current.Dispatcher), а так же все объекты, наследованные от System.Windows.Threading.DispatcherObject (это все контролы, окна, и даже все объект, кторые имеют зависимыс свойства(DependencyProperties)).
                  А если используется несколько потоков и из них необходимо править разные свойства десятка объектов(button, progress bar, listview), то для каждого надо прописывать такие события? Нет какого-то универсального решения?
                    Асинхронное программирование может чем то поможет.
                      Цитата mr.Torrens @
                      А если используется несколько потоков и из них необходимо править разные свойства десятка объектов(button, progress bar, listview), то для каждого надо прописывать такие события? Нет какого-то универсального решения?
                      - BackgroundWorker, SyncronizationContext. У меня на блоге немного инфы есть
                        Только что искал информацию по данной тематике для WPF. На первых строчках поисковика данная статья и еще вот эта:
                        http://msdn.microsoft.com/ru-ru/magazine/cc163328.aspx?ppud=4
                        Нашел все, что было нужно :rolleyes:
                        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                        0 пользователей:


                        Рейтинг@Mail.ru
                        [ Script execution time: 0,0310 ]   [ 16 queries used ]   [ Generated: 28.03.24, 15:08 GMT ]