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

С# Как фоновый поток сообщает потоку пользовательского интерфейса, что он закончил что-то делать?

Сценарий

Допустим, у вас есть приложение С# WinForms, которое выполняет некоторую обработку данных. У вас есть метод, который извлекает данные из базы данных, вызываемой потоком пользовательского интерфейса. Затем выполняется фоновый поток для выполнения этой задачи. Вы хотите, чтобы пользовательский интерфейс продолжал делать свое дело, а не быть заблокированным и не отвечающим.

Вопрос

Как вы можете отпустить фоновый поток и выполнить его обработку, а затем автоматически предупредить поток пользовательского интерфейса, когда он вернул результаты?

4b9b3361

Ответ 1

Если вы не используете поток background worker (по какой-либо причине), вы должны запустить событие из своего потока, который обрабатывается поток пользовательского интерфейса. Например, у меня есть этот код, который сканирует мои mp3, огонь и событие для каждого найденного альбома, а затем другое событие, когда оно закончилось (или остановлено):

    public void Build()
    {
        FindAlbums(Root);

        // Final update
        if (Library_Finished != null)
        {
            Library_Finished(this, null);
        }
    }

    private void FindAlbums(string root)
    {
        // Find all the albums
        string[] folders = Directory.GetDirectories(root);
        foreach (string folder in folders)
        {
            string[] files = Directory.GetFiles(folder, "*.mp3");
            if (files.Length > 0)
            {
                // Add to library - use first file as being representative of the whole album
                var info = new AlbumInfo(files[0]);
                if (Library_AlbumAdded != null)
                {
                    Library_AlbumAdded(this, new AlbumInfoEventArgs(info));
                }
            }

            FindAlbums(folder);
        }
    }

Затем в потоке пользовательского интерфейса (это код WinForms):

    private void Library_AlbumAdded(object sender, AlbumInfoEventArgs e)
    {
        if (dataGridView.InvokeRequired)
        {
            dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
        }
        else
        {
            AddToGrid(e.AlbumInfo);
        }
    }

    private void Library_Finished(object sender, EventArgs e)
    {
        if (dataGridView.InvokeRequired)
        {
            dataGridView.Invoke((MethodInvoker)delegate { FinalUpdate(); });
        }
        else
        {
            FinalUpdate();
        }
    }

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

Ответ 2

Существует несколько способов сделать это, но самый простой способ - использовать BackgroundWorker.

По существу, у него есть два делегата: DoWork и WorkCompleted. DoWork выполняется в отдельном потоке, и обратный вызов WorkCompleted происходит в потоке пользовательского интерфейса.

Здесь больше информации: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Ответ 3

Вы можете использовать BackgroundWorker для выполнения вашей обработки с интенсивным временем обработки в обработчике событий DoWork. Затем обработайте событие RunWorkerComplete - он загорится, когда закончен DoWork. Пока все это происходит, ваш поток пользовательского интерфейса будет успешно работать.

Ответ 4

Если вы говорите о приложении WinForm, вы можете вносить изменения в любые объекты пользовательского интерфейса, используя метод Invoke в вашей форме (или любой элемент управления в форме). Вы также можете найти полезное свойство InvokeRequired

Ответ 5

Если вы используете .NET 2.0 или новее, то это облегчается с помощью BackgroundWorker потока. У этого есть свое RunWorkerCompleted событие, которое делает только то, что вам нужно.

Я бы очень рекомендовал BackgroundWorker. Он имеет функциональность, которую разработчики выполняют после создания потоков. Они также легче отменить изящно, и у них даже есть возможность сообщить о прогрессе.

Ответ 6

Попробуйте использовать BackgrounWorker и зарегистрируйте обработчик для его события RunWorkerCompleted.

Ответ 7

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

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

Ответ 8

Вы можете сохранить ссылку на Диспетчер потоков пользовательского интерфейса с помощью Dispatcher.CurrentDispatcher(очевидно, в методе, вызванном потоком GUI). Используя этот объект, вы можете использовать методы BeginInvoke или Invoke в своем рабочем потоке для выполнения метода в потоке GUI, уведомляющего его о завершении работы. Лично я считаю, что этот метод будет немного более гибким, чем использование фонового рабочего объекта, и может создавать немного более читаемый код.

Ответ 9

В С# есть простой способ работы с несколькими потоками. Он называется BackgroundWorker. Вы должны проверить это: Фоновое задание

Ответ 10

Как уже упоминалось много раз, можно использовать класс BackgroundWorker.

В качестве альтернативы вы можете сделать что-то похожее на следующее:

void buttonGo_Clicked( object sender, EventArgs e )
{
    MyAsyncClass class = new MyAsyncClass();
    class.LongOperationFinished += (LongOperationFinishedEventHandler)finished;
    class.BeginLongOperation();
}

void finished( object sender, EventArgs e )
{
    if( this.InvokeRequired ) {
        this.BeginInvoke( (LongOperationFinishedEventHandler)finished, sender, e );
        return;
    }
    // You can safely modify the gui here.
}