Подтвердить что ты не робот

Как использовать многопоточность с Winform?

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Вы можете установить только свойства управления пользовательскими индикаторами выполнения из потока пользовательского интерфейса (WinForm one). Самый простой способ сделать это с помощью 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

ИЗМЕНИТЬ

Итак, я не понял вашего вопроса, я думал, что это связано с тем, что прогресс прогрессирует в ходе работы. Если это результат работы, используйте 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
    }
}

Ответ 2

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

void SetControlText(Control control, string text)
{
    if (control.InvokeRequired)
        control.Invoke(SetControlText(control, text));
    else
        control.Text = text;
}

Ответ 3

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

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

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

Ответ 5

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

вы просто пропустили некоторые моменты:

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

Надеюсь, я помог

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

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

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

Ответ 8

Сделайте так, создайте новый поток

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

         loginThread.Start();

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

         public delegate void DoWorkDelegate(ChangeControlsProperties);         

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

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

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

         InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm);

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

         this.Invoke(invokeDelegate);

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

Ответ 9

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

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

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

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