C# 从另一个线程调用TableAdapter.Fill时UI未更新
我正在用.NET4.0在C中开发一个MDI应用程序。 每个MDI子级都是一个带有选项卡的表单,其中包含带有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
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看看我更新的答案,这里有诀窍!