Исключение между потоками - Использование Invoke

Получив ошибку исключения между потоками, я нашел ее в MSDN.

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

addItemCallback d = new addItemCallback(addItem);

это находится в методе addItem () ниже.

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

private void startWork()
{        
    progressBar1.Value = 0;

    progressBar1.Maximum = 901242;

    backgroundWorker1.RunWorkerAsync();    
}

private void getList()
{
    if (pathFound)
    {
        for (int i = 0; i < numberOfPaths; i++)
        {
            Microsoft.Win32.RegistryKey mainPath = secondaryPath.OpenSubKey("application " + Convert.ToString(i));

            if (mainPath != null)
            {
                    this.addItem((string)mainPath.GetValue("Name"));
            }

            backgroundWorker1.ReportProgress(i);
        }
    }

    pathListBox.Sorted = true;
}

private void addItem(string item)
{
    if (this.pathListBox.InvokeRequired)
    {

        //addItemCallback d = new addItemCallback(addItem); 

        //not sure what this callBack is, can't get it to work, Callback isnt found.

        this.Invoke(d, new object[] { item });
    }

    else 
    {
        this.pathListBox.Items.Add(item);
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    getList();
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.progressBar1.Visible = false;
}

РАБОТАЙТЕ ТАК ДАЛЬШЕ

Код 1. Когда я использую background_doWork и вызываю из него метод, индикатор выполнения зависает в случайных местах и ​​перестает отвечать, при закрытии формы я получаю исключение объекта, поскольку я закрыл форму, пока она все еще пытается выполнить работу.

Код 2. Когда я помещаю весь код в background_doWork, а не вызываю из него метод, индикатор выполнения иногда срабатывает, каждую секунду или каждую 3-ю попытку запуска завершенной программы.

Что могло бы вызвать это?

----- КОД 1 -----------

    public Form1()
    {
        InitializeComponent();

        start();
    }

    int number = 900000;

    public void start()
    {
        progressBar1.Value = 0;

        progressBar1.Maximum = number;

        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        getList();
    }

    private void getList()
    {
        Microsoft.Win32.RegistryKey mainPath = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node");

        for (int i = 0; i < number; i++)
        {
            Microsoft.Win32.RegistryKey mainPath = secondaryPath.OpenSubKey("application " + Convert.ToString(i));

            if (mainPath != null)
            {
                this.addItem((string)mainPath.GetValue("Name"));
            }

            backgroundWorker1.ReportProgress(i);
        }
    }

    private void addItem(string item)
    {
        try
        {

            if (this.listBox1.InvokeRequired)
            {
                this.Invoke(new Action<string>(addItem), item);
            }

            else
            {
                this.listBox1.Items.Add(item);
            }
        }

        catch
        {
            MessageBox.Show("Error - Closed Object before it finished working.");
        }

        //this.steamGamesListBox.Sorted = true;
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Visible = false;
    }

------ КОД 2 --------

    public Form1()
    {
        InitializeComponent();

        start();
    }

    int number = 900000;

    public void start()
    {
        progressBar1.Value = 0;

        progressBar1.Maximum = number;

        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Microsoft.Win32.RegistryKey steamApps64 = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");

        for (int i = 0; i < number; i++)
        {
            Microsoft.Win32.RegistryKey steamApps = steamApps64.OpenSubKey("Steam App " + Convert.ToString(i));

            if (steamApps != null)
            {
                this.addItem((string)steamApps.GetValue("DisplayName"));
            }

            backgroundWorker1.ReportProgress(i);
        }
    }

    private void addItem(string item)
    {
        try
        {

            if (this.listBox1.InvokeRequired)
            {
                this.Invoke(new Action<string>(addItem), item);
            }

            else
            {
                this.listBox1.Items.Add(item);
            }
        }

        catch
        {
            MessageBox.Show("Error - Closed Object before it finished working.");
        }

        //this.steamGamesListBox.Sorted = true;
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Visible = false;
    }

person cheeseman    schedule 24.02.2012    source источник
comment
какую версию C # / Framework вы используете - в зависимости от этого вам либо нужно создать собственного делегата для Invoke, либо вы можете использовать что-то вроде Action   -  person Random Dev    schedule 24.02.2012
comment
используя .net4 :) (ограничение символов: P)   -  person cheeseman    schedule 24.02.2012


Ответы (2)


Вам нужно либо определить собственный тип делегата (ваш addItemCallback), либо просто использовать общий Action делегат:

private void addItem(string item)
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new Action<string>(addItem), item);
        return;
    }

    this.pathListBox.Items.Add(item);
}

Обычно это самый простой способ сделать это, поскольку вам не нужно вводить дополнительные типы делегатов.

Обратите внимание, что параметр object[] определяется с помощью ключевого слова params, поэтому вам не нужно создавать экземпляр нового массива object, а просто передать свои аргументы методу.

person Groo    schedule 24.02.2012
comment
Ах, это прекрасно работает! У меня только одна проблема, и когда я запускаю ее, список обновляется, но индикатор выполнения застревает посередине. Похоже, программа не может пройти через this.Invoke (new Action ‹string› (addItem), item); - как только я закрываю программу, я получаю исключение с удаленным объектом (потому что я его закрыл), есть идеи, почему он застревает здесь? - person cheeseman; 24.02.2012
comment
Обычно лучше использовать BeginInvoke вместо Invoke, чтобы избежать взаимоблокировок (проверьте мой обновленный пост). И я бы также удалил pathListBox.Sorted = true; из вашего фонового метода (вы не должны взаимодействовать с элементами управления из этого потока). Однако я не уверен, почему это произошло посередине. Я предполагаю, что вам следует вызывать backgroundWorker1.ReportProgress(100 * i / numberOfPaths);, но сейчас у вас, вероятно, есть около 50 путей, поэтому ваша обработка фактически заканчивается на этом (это мое предположение). Попробуйте внести эти изменения и посмотрите, как все пойдет. - person Groo; 24.02.2012
comment
Обратите внимание, что в этом случае вы можете использовать BeginInvoke, потому что порядок добавляемых элементов не важен, они будут вставлены на место, если для свойства Sorted установлено значение true. С другой стороны, если у вас есть проблемы с этим, вы можете попробовать полностью изменить структуру: используйте рабочий, чтобы заполнить List (вы можете сделать это внутри фонового потока), а затем передать список в ListBox, когда закончите . В зависимости от типа выполняемой работы и количества элементов это также может сработать. - person Groo; 24.02.2012
comment
Не делайте этого, это будет ужасно медленным и все равно приведет к кататоническому состоянию потока пользовательского интерфейса. Пусть ваш рабочий заполнит список элементов, вызовите pathListBox.Items.AddRange () в обработчике событий RunWorkerCompleted. - person Hans Passant; 24.02.2012
comment
при использовании invoke список обновляется по одному, а индикатор выполнения висит в случайных местах. Использование BeginInvoke исправило зависание, но по мере обновления списка он остается полностью белым, с увеличением вертикальной прокрутки, когда индикатор выполнения достигает конца, отображаются все значения: P - person cheeseman; 24.02.2012
comment
@HansPassant: что будет ужасно медленным? Разве ProgressChanged не вызывается также асинхронно (Post против Send)? - person Groo; 24.02.2012
comment
Begin / Invoke очень затратный, занимает около миллисекунды. Поток пользовательского интерфейса здесь безжалостно забивается этими запросами и больше не выполняет свои обычные обязанности. Нравится рисовать и реагировать на ввод. Он остается белым. - person Hans Passant; 24.02.2012
comment
@cheeseman: Это зависит от объема работы, проделанной между этими событиями. В вашем случае каждая итерация длится очень быстро, и у потока пользовательского интерфейса нет возможности перерисовать, как сказал Ханс. Возврат к Invoke действительно даст потоку достаточно времени для обновления. И вы должны получить ObjectDisposedException только в том случае, если вы закроете форму до того, как воркер завершит работу, поэтому вы, вероятно, не отменяете воркер в этом случае (вы должны сделать это внутри вашего FormClosing события или подобного, отметьте эта ветка для примера). - person Groo; 24.02.2012
comment
А, ладно, да, единственная причина, по которой я закрываю форму, - это то, что индикатор выполнения зависает, я понятия не имею, почему ... Он зависает в Invoke, но отлично работает в BeginInvoke. Единственная проблема связана с рисованием в BeginInvoke: P - person cheeseman; 24.02.2012
comment
Опубликованный вами код не выглядит так, как будто он должен зависать. Что еще происходит в этой форме? И полностью ли соответствует опубликованный вами код вашему фактическому коду? - person Groo; 24.02.2012
comment
Все то же самое, это была копипаст, просто добавлен вызов. Значение numberOfPaths в цикле - 901242, может ли это быть проблемой? - person cheeseman; 24.02.2012
comment
Что ж, вы, конечно, не должны жестко закодировать это значение внутри startWork(), а вместо этого используйте numberOfPaths, чтобы убедиться, что это то же самое значение. Вы должны попробовать использовать отладчик, чтобы узнать, где зависает ваше приложение. В этот момент прервите выполнение и используйте окно потоков, чтобы выбрать рабочий поток (местоположение должно быть внутри getList), и попробуйте проверить, где именно он ожидает. - person Groo; 24.02.2012
comment
Иногда я могу заставить его работать: P Добавил в свой основной пост код двух разных версий кода. Я не понимаю, почему иногда это срабатывает, а другие нет :( - person cheeseman; 24.02.2012

добавить delegate void addItemCallback(string item);

person Milan Halada    schedule 24.02.2012