Пул потоков: операция с несколькими потоками недопустима.

Я новичок в многопоточности, но получаю InvalidOperationException при использовании следующего кода. Я понимаю, что он пытается получить доступ к importFileGridView, но это было создано потоком пользовательского интерфейса, который создает исключение. Мой вопрос в том, как мне это решить? Возможно ли, чтобы GetAllImports имел возвращаемый тип? Как мне получить доступ к temp из моего потока пользовательского интерфейса?

ThreadPool.QueueUserWorkItem(new WaitCallback(GetAllImports), null);

private void GetAllImports(object x)
    {
        DataSet temp = EngineBllUtility.GetAllImportFiles(connectionString);
        if (temp != null)
            importFileGridView.DataSource = temp.Tables[0];
        else
            MessageBox.Show("There were no results. Please try a different search", "Unsuccessful", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

person ediblecode    schedule 29.12.2011    source источник


Ответы (2)


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

Вы можете справиться с этим через Control.Invoke или Control.BeginInvoke, например:

private void GetAllImports(object x)
{
    DataSet temp = EngineBllUtility.GetAllImportFiles(connectionString);
    if (temp != null)
    {
        // Use Control.Invoke to push this onto the UI thread
        importFileGridView.Invoke((Action) 
            () => 
            {
                importFileGridView.DataSource = temp.Tables[0];
            });
    }
    else
        MessageBox.Show("There were no results. Please try a different search", "Unsuccessful", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
person Reed Copsey    schedule 29.12.2011
comment
Обычно, когда я вижу этот код, он обычно проверяет, действительно ли Invoke требуется, прежде чем делать это (с .InvokeRequired). Есть ли в этой ситуации что-то особенное, что делает ее ненужной? - person M.Babcock; 29.12.2011
comment
@ M.Babcock M.Babcock Поскольку это делается непосредственно при вызове из ThreadPool.QueueUserWorkItem, вы заранее знаете, что находитесь в потоке ThreadPool. Это будет означать, что InvokeRequired ВСЕГДА будет возвращать true. Выставление чека становится ненужным чеком (в данном конкретном случае). Однако если вы пишете метод, который можно использовать как из потока пользовательского интерфейса, так и из других потоков, рекомендуется добавить проверку. - person Reed Copsey; 29.12.2011
comment
Спасибо за разъяснения. - person M.Babcock; 29.12.2011
comment
@ReedCopsey Простите, но я не понимаю, как это реализовать? - person ediblecode; 29.12.2011
comment
@user1016253 user1016253 Вы можете просто заменить свой код кодом выше, и это должно решить проблему... - person Reed Copsey; 29.12.2011
comment
@ user1016253 Какую ошибку вы видите? Какую версию C#/Visual Studio вы используете? - person Reed Copsey; 29.12.2011
comment
@ReedCopsey: вам нужно заставить поток графического интерфейса запускать код. Потоки GUI автоматически имеют Dispatcher. Что в основном делает диспетчер, так это запускает делегатов. Вызов метода Invoke() объекта поставит переданный вами делегат в очередь диспетчеру родительского потока. Это означает, что вызов invoke для вашей сетки поставит в очередь делегата в соответствующем потоке для запуска вашего кода обновления. - person Bengie; 29.12.2011

Что сказал Рид, но этот синтаксис мне нравится немного больше:

Что происходит, так это то, что вы создаете функцию делегата, которая будет передана в качестве параметра потоку пользовательского интерфейса через Control.Invoke, который вызывает его, таким образом, поток пользовательского интерфейса вносит изменения в importFileGridView.

importFileGridView.Invoke((MethodInvoker) delegate {
                             importFileGridView.DataSource = temp.Tables[0];
                         });

Вы также можете написать это так:

//create a delegate with the function signature
public delegate void SetDateSourceForGridViewDelegate (GridView gridView, Object dataSource);

//write a function that will change the ui
public void SetDataSourceForGridView(GridView gridView, Object dataSource)
{
    gridView.DataSource = dataSource;
}

//Create a variable that will hold the function
SetDateSourceForGridViewDelegate delegateToInvoke = SetDataSourceForGridView;

//tell the ui to invoke the method stored in the value with the given paramters.
importFileGridView.Invoke(delegateToInvoke, importFileGridView, temp.Tables[0]);

и я бы посоветовал использовать MethodInvoker вместо Action см.: здесь

person albertjan    schedule 29.12.2011