Пользовательский интерфейс не обновляется, когда TableAdapter.Fill вызывается из другого потока

1

Я разрабатываю приложение MDI в С# с .NET 4.0. Каждый ребенок MDI будет формой с вкладками, которые содержат GroupBoxes с DataGridView. Я реализовал класс, который используется для управления Threads.

Это метод StartNewThread в моем классе ThreadManager

public string StartNewThread(ThreadStart threadMethod, string threadName)
{
    try
    {
        Thread thread = new Thread(() => threadMethod());
        thread.Name = threadName + " (" + _threadCount++.ToString("D4") + ")";
        thread.Start();
        _threadList.Add(thread.Name, thread);

        return thread.Name;
    }
    catch (Exception ex)
    {
        //Log and manage exceptions
    }

    return null;
}

Чтобы создать DataGridViews, я использовал компонент Wizard из инструментов Oracle Developer Tools для библиотеки VS. Итак, после создания DataSource и, таким образом, DataSet, я использовал drag & drop из дерева DataSource для перетаскивания таблиц и автоматического создания DataGridView.

Это фактический рабочий код, за дочерней формой, автоматически создается.

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

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }
}

Теперь я хочу управлять всеми операциями load/query/insert/update/delete в отдельных потоках. На данный момент я попытался создать новый поток для загрузки данных.

Это я пробовал.

public partial class ScuoleNauticheForm : Form
{
    private readonly ThreadManager _threadManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _threadManager = ThreadManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _threadManager.StartNewThread(LoadData, "LoadData");
    }

    #region DataBind

    private void LoadData()
    {
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }

    #endregion
}

Он работает только на половину... Нет ошибок или исключений, но если я загружаю данные таким образом, используя другой Thread, DataGridviews не обновляется, и я не вижу никаких данных при открытии формы, даже если я перемещаюсь или изменить его размер. В противном случае, используя автоматически сгенерированный код, DataGridViews заполняются правильно. Но поскольку мастер также добавляет панель навигации в форму для навигации по записям, я заметил, что она работает, потому что она подсчитывает правильное количество записей, и я могу использовать стрелки (сначала, предыдущий, следующий, последний) для перемещения по записей.

Вот изображение, показывающее мою форму. См. Панель навигации, в которой отображается правильное количество общих записей (14), и позволяет мне перемещаться по ним.

Изображение 174551

Нужно ли мне использовать delegates? Если это так, я думаю, что это будет беспорядок... сколько delegates я должен создать и для этих методов? Или есть другое решение?

- ОБНОВЛЕНИЕ 1 -

Я знаю, что потоки пользовательского интерфейса автоматически управляются.NET и поэтому программисту не нужно управлять ими с помощью кода. Итак, должна ли быть проблема синхронизации с потоком.NET UI, встроенным в управление? Может быть, мой поток, запущенный Form.Load() вмешивается в поток пользовательского интерфейса, управляемый.NET?

- ОБНОВЛЕНИЕ 2 -

Я попытался реализовать решение, предложенное faby. Я заменил логику Thread логикой Task. Поведение приложения одинаковое, поэтому все, что работает с Thread, теперь также работает с Task. Но проблема все еще остается. Поскольку я на.NET 4.0, а не.NET 4.5, я не мог использовать async и ждать. Поэтому я не знаю, будет ли с этим подходом работать пользовательский интерфейс или нет. Любое другое предложение, действительное для.NET 4.0?

  • 0
    посмотрите на мой обновленный ответ, тут есть хитрость!
Теги:
multithreading
user-interface
datagridview

2 ответа

0
Лучший ответ

Я наконец нашел решение без использования async/wait и других библиотек. Проблема заключалась в том, что я выполнял метод Fill() TableAdapter внутри новой Task поэтому мне нужно было использовать InvokeRequired для установки источника данных источника привязки в DataTable в правильном потоке.

Поэтому я использовал delegates. Я изменил метод, вызванный новой Задачей, и заставил его вызвать 3 других метода (по одному для каждого DataGridView для заполнения), которые вызывают Fill() реализующие проверку InvokeRequired.

Теперь я вижу создание пользовательского интерфейса, а затем через пару секунд асинхронное заполнение DataGridView.

Эта статья была полезной: загрузка данных из TableAdapter async

Спасибо @faby за предложение использовать Task вместо Thread. Это не было решением, но это лучший способ сделать Threading.

Вот окончательный рабочий код.

public partial class ScuoleNauticheForm : Form
{
    private readonly TaskManager _taskManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _taskManager = TaskManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _taskManager.StartNewTask(LoadData);
    }

    #region Delegates

    public delegate void FillPersonaleCallBack();
    public delegate void FillNatantiCallBack();
    public delegate void FillScuoleCallBack();

    #endregion

    #region DataBind

    private void LoadData()
    {
        FillPersonale();
        FillNatanti();
        FillScuole();
    }

    public void FillPersonale()
    {
        if (PersonaleDataGridView.InvokeRequired)
        {
            FillPersonaleCallBack d = new FillPersonaleCallBack(FillPersonale);
            Invoke(d);
        }
        else
        {
            this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        }
    }

    public void FillNatanti()
    {
        if (NatantiDataGridView.InvokeRequired)
        {
            FillNatantiCallBack d = new FillNatantiCallBack(FillNatanti);
            Invoke(d);
        }
        else
        {
            this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        }
    }

    public void FillScuole()
    {
        if (ScuoleDataGridView.InvokeRequired)
        {
            FillScuoleCallBack d = new FillScuoleCallBack(FillScuole);
            Invoke(d);
        }
        else
        {
            this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
        }
    }

    #endregion
}

- Обновление 1 -

Если методы для вызова новой Задачей являются void и без каких-либо параметров, вы можете немного упростить приведенный выше код, используя Invoke((MethodInvoker) MethodName). Поведение приложения одинаковое.

Вот упрощенная версия кода.

public partial class ScuoleNauticheForm : Form
{
    private readonly TaskManager _taskManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _taskManager = TaskManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _taskManager.StartNewTask(LoadData);
    }

    #region DataBind

    private void LoadData()
    {
        // Since Fill Methods are void and without parameters,
        // you can use the Invoke method without the need to specify delegates.
        Invoke((MethodInvoker)FillPersonale);
        Invoke((MethodInvoker)FillNatanti);
        Invoke((MethodInvoker)FillScuole);
    }

    public void FillPersonale()
    {
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
    }

    public void FillNatanti()
    {
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
    }

    public void FillScuole()
    {
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }

    #endregion
}
1

вы считаете вариант BackgroundWorker Class?

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

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            //long running task

        }


        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //update the UI components
        }

обновление 1

другое решение может быть чем-то вроде этого

public Task LoadDataAsync()
{
    return Task.Factory.StartNew( () =>
    {
        //code to fill your datagridview
    });
}

тогда

public async Task ChangeUIComponents()
{
    await LoadDataAsync();

    // now here you can refresh your UI elements           
}

обновление 2

для использования async/await с фреймворком 4.0 попробуйте этот NugetPackage (Microsoft.Bcl.Async)

  • 0
    Я не очень Threading , поэтому я начал с класса Thread потому что я уже использовал его в прошлом. Я действительно не знаю, будет ли BackgroundWorker лучшим подходом и совместим ли он с моей логикой. Есть также ThreadPool и TPL ... но в любом случае, переход на BackgroundWorker или что-то еще заставит меня также изменить логику, которую я уже сделал. Вы уверены, что у меня не будет такой же проблемы при использовании BackgroundWorker ?
  • 0
    Как насчет моего второго подхода? Это соответствует вашим потребностям?
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню