2010-01-19 2 views
6

У меня есть проект для домашних животных, над которым я работаю, у которого есть несколько рабочих потоков. Вывод всего на консоль становится все труднее, поэтому я хочу разработать интерфейс, который будет иметь одну область вывода на поток. Я хочу знать, как наилучшим образом потоки могут отправлять обновления в пользовательский интерфейс. У меня есть две идеи:Обновление пользовательского интерфейса из нескольких рабочих потоков (.NET)

1) У каждого потока установлен флаг «DataUpdated», когда доступны новые данные, и пользовательский интерфейс периодически проверяет наличие новых данных.

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

В настоящее время я склоняюсь к (2) по двум причинам: мне не нравится идея «проверки» каждого потока, и потому что это мое первое многопоточное приложение и (2) кажется более простым, чем это возможно. Я хочу знать:

  • Какой вариант предпочтительнее с точки зрения простоты и эффективности?
  • У вас есть какие-либо советы по реализации (2) или что-то в этом роде (т. Е. Больше событий)?

ответ

12

Вы можете легко реализовать (2) путем создания компонентов BackgroundWorker и делает работу в своих обработчиков DoWork:

BackgroundWorker bw = new BackgroundWorker(); 
bw.WorkerReportsProgress = true; 
bw.DoWork += /* your background work here */; 
bw.ProgressChanged += /* your UI update method here */; 
bw.RunWorkerAsync(); 

Каждый BackgroundWorker может сообщить о прогрессе в потоке пользовательского интерфейса, вызвав ReportProgress: хотя это в первую очередь предназначен для отчет о прогрессе в ограниченном процессе, это необязательно - вы также можете передавать свои собственные пользовательские данные, если это требует ваше обновление пользовательского интерфейса. Вы бы назвали ReportProgress из вашего DoWork-обработчика.

Приятная вещь в BackgroundWorker заключается в том, что он заботится о множестве грязных кросс-резьбовых деталей для вас. Он также соответствует управляемой событиям модели обновлений, которые вы (по праву) предпочитаете явным обратным вызовам.

+2

Отлично ... спасибо! Теперь мне просто нужно подождать, чтобы оставить свою дневную работу, чтобы я мог заниматься каким-то настоящим программированием. – iandisme

+0

Выполняйте эту работу в Compact framework (3.5). – Royal

1

Я голосую за # 2, но с BackgroundWorkers вместо System.Threading.Threads.

+0

И функция обратного вызова должна быть поточно ... – Oded

0

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

This статья дает краткий обзор.

0

Предпочтительным способом реализации многопоточности в приложении является использование компонента BackgroundWorker. Компонент BackgroundWorker использует управляемую событиями модель для многопоточности. Рабочий поток запускает обработчик событий DoWork, а поток, создающий ваши элементы управления, запускает обработчики событий ProgressChanged и RunWorkerCompleted.
Когда вы обновляете элементы управления пользовательским интерфейсом в обработчике событий ProgressChanged, они автоматически обновляются в основном потоке, что предотвратит получение исключений crossthread.

Посмотрите here на пример использования фонового рисунка.

1

В большинстве случаев проще всего использовать компонент BackgroundWorker, как предлагается в ответе itowlson, и я настоятельно рекомендую использовать этот подход, если это возможно. Если по какой-то причине вы не можете использовать компонент BackgroundWorker для своей цели, например, если вы работаете с .Net 1.1 (yikes!) Или с компактной структурой, то вам, возможно, придется использовать альтернативный подход:

С Winform контролирует вы должны избегать изменений управления на любом потоке, отличных от потока, который первоначально созданного элемент управления. Компонент BackgroundWorker обрабатывает это для вас, но если вы не используете это, то вы можете и должны использовать свойство InvokeRequired и Invoke метод нашел на классе System.Windows.Forms.Control. Ниже приведен пример, который использует это свойство и метод:

public partial class MultithreadingForm : Form 
{ 
    public MultithreadingForm() 
    { 
     InitializeComponent(); 
    } 

    // a simple button event handler that starts a worker thread 
    private void btnDoWork_Click(object sender, EventArgs e) 
    { 
     Thread t = new Thread(WorkerMethod); 
     t.Start(); 
    } 

    private void ReportProgress(string message) 
    { 
     // check whether or not the current thread is the main UI thread 
     // if not, InvokeRequired will be true 
     if (this.InvokeRequired) 
     { 
      // create a delegate pointing back to this same function 
      // the Invoke method will cause the delegate to be invoked on the main UI thread 
      this.Invoke(new Action<string>(ReportProgress), message); 
     } 
     else 
     { 
      // txtOutput is a UI control, therefore it must be updated by the main UI thread 
      if (string.IsNullOrEmpty(this.txtOutput.Text)) 
       this.txtOutput.Text = message; 
      else 
       this.txtOutput.Text += "\r\n" + message; 
     } 
    } 

    // a generic method that does work and reports progress 
    private void WorkerMethod() 
    { 
     // step 1 
     // ... 
     ReportProgress("Step 1 completed"); 

     // step 2 
     // ... 
     ReportProgress("Step 2 completed"); 

     // step 3 
     // ... 
     ReportProgress("Step 3 completed"); 
    } 
} 
0

Если вы создаете свои собственные темы (не BackgroundWorker или Threadpool нитей), вы можете передать метод обратного вызова из вашего основного потока, который вызывается из рабочего потока. Это также позволяет передавать аргументы для обратного вызова и даже возвращать значение (например, флаг go/no-go). В обратном вызове при обновлении пользовательского интерфейса через диспетчер целевого элемент управления:

public void UpdateUI(object arg) 
{ 
    controlToUpdate.Dispatcher.BeginInvoke(
     System.Windows.Threading.DispatcherPriority.Normal 
     , new System.Windows.Threading.DispatcherOperationCallback(delegate 
     { 
      controToUpdate.property = arg; 
      return null; 
     }), null); 
    } 
} 
Смежные вопросы