C# 从另一个线程调用TableAdapter.Fill时UI未更新

C# 从另一个线程调用TableAdapter.Fill时UI未更新,c#,multithreading,oracle,user-interface,datagridview,C#,Multithreading,Oracle,User Interface,Datagridview,我正在用.NET4.0在C中开发一个MDI应用程序。 每个MDI子级都是一个带有选项卡的表单,其中包含带有DataGridView的GroupBox。 我实现了一个用于管理线程的类 这是我的ThreadManager类中的StartNewThread方法 public string StartNewThread(ThreadStart threadMethod, string threadName) { try { Thread thread = new Thre

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

这是我的
ThreadManager
类中的
StartNewThread
方法

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;
}
为了创建DataGridView,我使用了Oracle Developer Tools for VS库中的一些向导组件。因此,在创建数据源和数据集之后,我使用数据源树中的拖放来拖动表并自动创建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);
    }
}
我现在要做的是管理在分离线程上的所有加载/查询/插入/更新/删除操作。现在,我尝试创建一个新线程来加载数据

这就是我所尝试的

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
}
它只对一半有效。。。没有错误或异常,但是如果我以这种方式加载数据,使用不同的
线程
,打开表单时DataGridView不会更新,也看不到任何数据,即使我移动或调整了表单的大小。否则,将使用自动生成的代码正确填充DataGridView。 但是,由于向导还向表单中添加了导航栏来浏览记录,我注意到它是有效的,因为它计算了正确的记录数,并且我可以使用箭头(第一个、上一个、下一个、最后一个)在记录之间移动

这是一张显示我的表格的图片。 查看显示正确总数记录(14)的导航栏,并允许我浏览这些记录

我是否需要使用
代理
?如果是这样的话,我想那将是一片混乱。。。我应该为这些方法创建多少个
委托
?还是有其他解决办法

--更新1--

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

--更新2--

我试图实现faby提出的解决方案。我将
线程
逻辑替换为
任务
逻辑。应用程序的行为是相同的,因此所有与
线程
一起工作的东西现在也与
任务
一起工作但问题仍然存在。因为我使用的是.NET 4.0而不是.NET 4.5,所以我无法使用async并等待。所以我不知道用这种方法UI是否能正常工作。
对于.NET 4

> P>是否有其他的建议? 实现
DoWork
ProgressChanged
您可以在
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
    });
}
然后

更新2


要在framework 4.0中使用async/await,请尝试使用NugetPackage(
Microsoft.Bcl.async

我终于找到了一个解决方案,而不使用async/await和其他库。 问题是我在一个新的
任务中执行
TableAdapter
Fill()
方法,因此我需要使用
invokererequired
将绑定源数据源设置到右线程中的
DataTable

所以我使用了
代理
。我更改了在新任务上调用的方法,并使其调用3个其他方法(每个
DataGridView
调用一个方法来填充),这些方法调用
fill()
实现
invokererequired
检查

现在我看到了UI的创建,几秒钟后,DataGridView的异步填充

这篇文章很有用:

感谢@faby建议使用任务而不是线程。这不是解决方案,但它是一种更好的线程方式

这是最后的工作代码

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

如果新任务要调用的方法是
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 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
}
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
}

我不太擅长
线程
,所以我从
线程
类开始,因为我过去已经使用过。我真的不知道
BackgroundWorker
是否是更好的方法,也不知道它是否符合我的逻辑。还有
ThreadPool
TPL
。。。但无论如何,换成BackgroundWorker或其他什么会迫使我也改变我已经做出的逻辑。您确定使用
BackgroundWorker
不会出现同样的问题吗?我的第二种方法呢?它是否适合您的需要?我尝试用
任务
逻辑更改
线程
逻辑。我找到了那些文章:还有。看来
Task
方法是处理线程的最现代、最简单的方法。逻辑几乎相同,只是任务没有名称,而是自动生成一个唯一的标识符编号。因此,将对
ThreadManager.StartNewThread
的调用替换为
TaskManager.StartNewTask
非常简单且有效。。。但是我的问题仍然是
任务
太多了!!!另外,我不能使用
async
await
,因为我使用的是.NET4.0而不是4.5!我已经更新了我的答案,请尝试使用该软件包在Framework4.0中使用async/await看看我更新的答案,这里有诀窍!