Обновление пользовательского интерфейса из нескольких рабочих потоков (.NET) - PullRequest
6 голосов
/ 20 января 2010

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

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

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

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

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

Ответы [ 6 ]

12 голосов
/ 20 января 2010

Вы можете легко реализовать (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 заключается в том, что он позаботится о многих беспорядочных деталях перекрестного потока для вас.Он также соответствует управляемой событиями модели обновлений, которую вы (справедливо) предпочитаете явным обратным вызовам.

1 голос
/ 20 января 2010

В большинстве случаев проще всего было бы использовать компонент BackgroundWorker, как предложено в ответе Итоулсона, и я настоятельно рекомендую использовать этот подход, если это возможно. Если по какой-то причине вы не можете использовать компонент 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");
    }
}
1 голос
/ 20 января 2010

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

0 голосов
/ 20 января 2010

Если вы создаете свои собственные потоки (не фоновые потоки или потоки 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);
    }
} 
0 голосов
/ 20 января 2010

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

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

0 голосов
/ 20 января 2010

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

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