2014-12-03 117 views
1

叫我正在开发中C#MDI应用与.NET 4.0。 每个MDI子项将是一个带有标签的窗体,其中包含带有DataGridView的组框。 我实现了一个用于管理线程的类。UI没有更新时TableAdapter.Fill从另一个线程

这是我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; 
} 

要创建我使用了一些向导组件从Oracle开发工具VS库DataGridViews的StartNewThread方法。所以,在创建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不更新,并打开时,我没有看到任何数据表格,即使我移动或调整它的大小。否则,使用自动生成的代码,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线程?

- UPDATE 2 -

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

+0

看看我更新的答案,有一招! – faby 2014-12-03 16:02:25

回答

0

我终于找到了解决而不使用异步/等待和其他库。 问题是我在内执行TableAdapterFill()方法,所以我需要使用InvokeRequired在正确的线程中将绑定源数据源设置为DataTable

所以我用delegates。我更改了新任务上调用的方法,并调用其他3种方法(每个方法填写DataGridView),调用Fill()执行InvokeRequired检查。

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

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

感谢@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 
} 

- 更新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?

实施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

使用异步/ AWAIT与框架4.0尝试用this NugetPackage(Microsoft.Bcl.Async

+0

我并不是很擅长'Threading',所以我从'Thread'类开始,因为我已经使用过。我真的不知道'BackgroundWorker'是否会更好,如果它符合我的逻辑。还有'ThreadPool'和'TPL' ......但无论如何,更改为BackgroundWorker或其他内容将迫使我也改变我已经制作的逻辑。你确定我不会使用'BackgroundWorker'出现同样的问题吗? – 2014-12-03 13:28:24

+0

我的第二种方法呢?它是否符合您的需求? – faby 2014-12-03 13:55:55

+0

我试图用'Task'逻辑改变我的'Thread'逻辑。我发现这些文章:[任务](http://dotnetcodr.com/2014/01/01/5-ways-to-start-a-task-in-net-c/)和[任务与线程](http ://stackoverflow.com/questions/13429129/task-vs-thread-differences)。看起来,“任务”方法是进行线程处理的现代和最简单的方法。逻辑几乎相同,只是任务没有名字,而是自动生成的唯一标识号。因此,用'TaskManager.StartNewTask'替换我对'ThreadManager.StartNewThread'的调用很简单,并且工作正常......但是我的问题仍然存在于'Task'中! – 2014-12-03 15:48:21