从另一个线程调用TableAdapter.Fill时UI不更新

时间:2014-12-03 11:55:37

标签: c# multithreading oracle user-interface datagridview

我正在使用 .NET 4.0 C#中开发 MDI 应用程序。 每个MDI子项都是一个包含选项卡的表单,其中包含DataGridView的GroupBox。 我实现了一个用于管理线程的类。

这是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,我使用了 Oracle Developer Tools for VS 库中的一些向导组件。因此,在创建了DataSource以及DataSet之后,我使用了从DataSource树拖放来拖动表并自动创建DataGridViews。

这是自动创建的子表单后面的实际工作代码

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);
    }
}

我现在要做的是管理分离线程上的所有加载/查询/插入/更新/删除操作。现在我试图创建一个新的线程来加载数据。

这就是我的尝试。

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不会更新,我不会#&# 39;打开表单时看到任何数据,即使我移动或调整它。否则,使用自动生成的代码,将正确填充DataGridViews。 但是,由于向导还在表单中添加导航栏以浏览记录,我注意到它有效,因为它计算了正确的记录数,我可以使用箭头(第一个,上一个,下一个,最后一个)来移动记录。

这是显示我的表单的图像。 请参阅显示正确记录总数的导航栏(14),并允许我浏览它们。

See the navigation bar that is showing the correct number of total records and allows me to navigate through them.

我需要使用delegates吗?如果是这样,我认为这将是一团糟...我应该创建多少delegates和那些方法?还是有另一种解决方案吗?

- 更新1 -

我知道UI线程由.NET自动管理,因此程序员不需要使用代码来管理它们。那么,它应该是与管理中内置的.NET UI线程同步的问题吗?也许我的Form.Load()启动的线程会干扰.NET管理的UI线程?

- 更新2 -

我试图实施faby提出的解决方案。我用Thread逻辑替换了Task逻辑。应用程序的行为是相同的,因此使用Thread的所有内容现在也与Task一起使用。 但问题仍然存在。由于我在.NET 4.0 而不是.NET 4.5,我无法使用async和await。所以我不知道用这种方法UI是否能正常工作。 任何其他建议对.NET 4.0有效

2 个答案:

答案 0 :(得分:1)

你考虑BackgroundWorker Class的选项吗?

实施DoWorkProgressChanged您可以在DoWork中执行您在后台线程中执行的操作,在ProgressChanged中可以更新UI

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

在框架4.0中使用async / await尝试使用this NugetPackage(Microsoft.Bcl.Async

答案 1 :(得分:0)

我终于找到了一个解决方案,而不使用async / await 和其他库。 问题是我在新Fill()内执行了TableAdapter Task方法,所以我需要使用InvokeRequired将绑定源数据源设置为{{} 1}}在正确的主题中。

所以我使用了DataTable。我更改了在新任务上调用的方法,并调用其他3个方法(每个delegates填充一个),调用DataGridView实现Fill()检查。

现在我看到UI的创建,然后在几秒钟之后,异步填充DataGridViews。

这篇文章很有用:Load data from TableAdapter async

感谢@faby建议使用Task而不是Thread。这不是解决方案,但它是一种更好的线程化方法。

这是最终工作代码

InvokeRequired

- 更新1 -

如果新任务调用的方法是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 } 且没有任何参数,则可以使用void简化上述代码。应用程序的行为是相同的。

这是代码的简化版

Invoke((MethodInvoker) MethodName)