Как использовать многопоточность с Winform? - PullRequest
14 голосов
/ 16 июня 2011

Я новичок с многопоточностью. У меня есть winform, у которого есть метка и индикатор выполнения.

Я хочу показать результат обработки. Во-первых, я использую Application.DoEvents() метод. Но я считаю, что форма замерзает.

Затем я прочитал статью о многопоточности в MSDN.

Во-вторых, я использую BackgroundWorker для этого.

this.bwForm.DoWork += (o, arg) => { DoConvert(); };
this.bwForm.RunWorkerAsync();

Форма не останавливается, что я могу перетаскивать при обработке. К сожалению, он генерирует исключение InvalidOperationException. Поэтому я должен использовать это. Control.CheckForIllegalCrossThreadCalls = false; Я уверен, что это не окончательное решение.

У вас есть предложение, эксперты?

Edit: Когда я вызываю listview, выбрасываю InvalidOperationException. Этот код находится в DoWork ().

          foreach (ListViewItem item in this.listView1.Items)
            {
                //........some operation
                 lbFilesCount.Text = string.Format("{0} files", listView1.Items.Count);
                 progressBar1.Value++;
            }

Edit2: Я использую делегат и вызывать в DoWork (), и это не исключение. Но форма снова замерзает. Как это сделать, чтобы форму можно было перетаскивать во время обработки?

Ответы [ 9 ]

9 голосов
/ 21 июня 2011

Вы можете установить свойства пользовательского элемента управления индикатора выполнения только из потока пользовательского интерфейса (WinForm). Самый простой способ сделать это с помощью BackgroundWorker - использовать событие ProgressChanged:

private BackgroundWorker bwForm;
private ProgressBar progressBar;

В конструкторе WinForm:

this.progressBar = new ProgressBar();
this.progressBar.Maximum = 100;
this.bwForm = new BackgroundWorker();
this.bwForm.DoWork += new DoWorkEventHandler(this.BwForm_DoWork);
this.bwForm.ProgressChanged += new ProgressChangedEventHandler(this.BwForm_ProgressChanged);
this.bwForm.RunWorkerAsync();

...

void BwForm_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bgw = sender as BackgroundWorker;
    // Your DoConvert code here
    // ...          
    int percent = 0;
    bgw.ReportProgress(percent);
    // ...
}

void BwForm_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar.Value = e.ProgressPercentage;
}

см. Здесь: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

EDIT

Так что я не понял вашего вопроса, я подумал, что нужно показать прогресс-бар во время работы. Если речь идет о результате работы, используйте e.Result в событии BwForm_DoWork. Добавить новый обработчик события для завершенного события и управлять результатом:

this.bwForm.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.BwForm_RunWorkerCompleted);

...

private void BwForm_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    YourResultStruct result = e.Result as YourResultStruct;
    if (e.Error != null && result != null)
    {
        // Handle result here
    }
}
9 голосов
/ 16 июня 2011

Вызвать поток пользовательского интерфейса.Например:

void SetControlText(Control control, string text)
{
    if (control.InvokeRequired)
        control.Invoke(SetControlText(control, text));
    else
        control.Text = text;
}
6 голосов
/ 16 июня 2011

Избегайте звонить Application.DoEvents.Часто это приводит к большему количеству проблем, чем решает.Обычно это считается плохой практикой, потому что существуют лучшие альтернативы, которые сохраняют сообщения прокачки пользовательского интерфейса.

Избегайте изменения настройки CheckForIllegalCrossThreadCalls = false.Это ничего не исправит.Это только маскирует проблему.Проблема в том, что вы пытаетесь получить доступ к элементу пользовательского интерфейса из потока, отличного от основного потока пользовательского интерфейса.Если вы отключите CheckForIllegalCrossThreadCalls, то вы больше не получите исключение, а вместо этого ваше приложение будет непредсказуемо и эффектно сбоить.

Из обработчика событий DoWork вам потребуется периодически вызывать ReportProgress.С вашего Form вы захотите подписаться на событие ProgressChanged.Доступ к элементам пользовательского интерфейса будет безопасным из обработчика событий ProgressChanged, поскольку он автоматически маршалируется в поток пользовательского интерфейса.

5 голосов
/ 23 июня 2011

Самый чистый способ использует BackGroundWorker, как вы это сделали.

Вы только что пропустили несколько очков:

  1. вы не можете получить доступ к элементам формы в DoWork обработчике событий, потому что это вызывает метод перекрестного потока, это должно быть сделано в ProgressChanged обработчике событий.
  2. по умолчанию BackGroundWorker не позволит сообщать о прогрессе, а также не позволяет отменить операцию. Когда вы добавляете BackGroundWorker к своему коду, вы должны установить свойство WorkerReportsProgress этого BackGroundWorker на true, если вы хотите вызвать ReportProgress метод вашего BackGroundWorker .
  3. Если вам нужно разрешить пользователю отменять операцию, установите WorkerSupportsCancellation в true и в своем цикле в обработчике события DoWork проверьте свойство с именем CancellationPending в вашем BackGroundWorker

Я надеюсь, что помог

5 голосов
/ 16 июня 2011

Вы должны использовать Control.Invoke () .Также см. этот вопрос для деталей.

4 голосов
/ 24 июня 2011

Есть некоторые фундаментальные проблемы с кодом, который вы показали.Как уже упоминалось, Application.DoEvents() не будет делать то, что вы хотите от фонового потока.Разделите медленную фоновую обработку в BackgroundWorker и обновите индикатор выполнения в потоке пользовательского интерфейса.Вы звоните progressBar1.Value++ из фонового потока, что неверно.

Никогда не вызывайте Control.CheckForIllegalCrossThreadCalls = false, это только сократит ошибки.

Если вы хотите обновить индикатор выполнения из фонапоток, вам придется реализовать обработчик ProgressChanged;вы этого не делаете.

Если вам нужен пример реализации BackgroundWorker, обратитесь к статье Потоки BackgroundWorker из Code Project.

3 голосов
/ 26 июня 2011

Я бы сказал, что написание многопоточных приложений - одна из самых сложных вещей для понимания и правильного выполнения. Вы действительно должны прочитать об этом. Если вы узнали C # из книги, вернитесь и посмотрите, нет ли главы о многопоточности. Я узнал из книг Эндрю Троелсена (последняя была Pro C # 2005 и платформа .NET 2.0 ), он не затрагивает эту тему до главы 14 (поэтому к тому времени многие перестали читать). Я пришел из опыта встраиваемого программирования, где параллелизм и атомарность также являются проблемой, так что это не проблема .NET или Windows.

Многие из этих постов подробно описывают механизм работы с потоками с помощью средств, поставляемых в .NET. Все ценные советы и вещи, которые вы должны выучить, но это действительно поможет, если вы сначала лучше познакомитесь с «теорией». Возьмите проблему с многопоточностью. Что действительно происходит, так это то, что поток пользовательского интерфейса имеет некоторую встроенную сложность, которая проверяет, изменяете ли вы элемент управления из другого потока. Дизайнеры MS поняли, что сделать ошибку было легко, поэтому была встроенная защита от нее. Почему это опасно? Это требует, чтобы вы поняли, что такое атомарная операция. Так как «состояние» элемента управления не может быть изменено в элементарной операции, один поток может начать изменять элемент управления, а затем другой поток может стать активным, оставляя элемент управления в частично измененном состоянии. Теперь, если вы скажете пользовательскому интерфейсу, что я не даю дерьма, просто позвольте моему потоку изменить элемент управления, он может работать. Однако, когда это не сработает, вам будет очень сложно найти ошибку. Вы сделаете свой код намного менее обслуживаемым, и программисты, которые последуют за вами, будут проклинать ваше имя.

Итак, пользовательский интерфейс сложен и проверяет вас, а классы и потоки, которые вы пишете, - нет. Вы должны понимать, что является атомным в ваших классах и потоках. Вы можете быть причиной "заморозки" в ваших темах. Чаще всего зависание приложения является результатом тупиковой ситуации. В этом случае «заморозить» означает, что пользовательский интерфейс никогда не станет реагировать снова. В любом случае это становится слишком длинным, но я думаю, что как минимум вы должны быть знакомы с использованием «блокировки», а также, вероятно, Monitor, interlocked, семафоров и мьютексов.

Просто чтобы дать вам идею: (из книги Троелсена)

intVal++; //This is not thread safe 
int newVal = Interlocked.Increment(ref intVal); //This is thread safe

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

2 голосов
/ 16 июня 2011

Сделайте так, создайте новую тему

         Thread loginThread = new Thread(new ThreadStart(DoWork));

         loginThread.Start();

Внутри ThreadStart () передайте метод, который вы хотите выполнить. Если внутри этого метода вы хотите изменить некоторые свойства элементов управления, создайте делегат и укажите его на метод, внутри которого вы будете писать измененные свойства элементов управления,

         public delegate void DoWorkDelegate(ChangeControlsProperties);         

и вызовите свойства элементов управления, сделайте так, объявите метод и внутри него определите элементы управления новыми свойствами

         public void UpdateForm()
         {
             // change controls properties over here
         }

затем укажите делегату метода, таким образом,

         InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm);

тогда, когда вы хотите изменить свойства в любом месте, просто позвоните,

         this.Invoke(invokeDelegate);

Надеюсь, этот фрагмент кода поможет вам! :)

1 голос
/ 24 июня 2011

Если добавить к решению свободы, он был прав. Это решение thread-safe, и это именно то, что вы хотите. Вам просто нужно использовать BeginInvoke вместо Invoke.

void SetControlText(Control control, string text)
{
    control.BeginInvoke(
        new MethodInvoker(() =>
        {
            control.Text = text;
        })
    );
}

Данное исправление является самым простым и чистым.

Надеюсь, это поможет. :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...