2014-12-03 58 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