Я разрабатываю приложение MDI в С# с .NET 4.0. Каждый ребенок MDI будет формой с вкладками, которые содержат GroupBoxes с DataGridView
. Я реализовал класс, который используется для управления Threads.
Это метод 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, я использовал компонент Wizard из инструментов Oracle Developer Tools для библиотеки VS. Итак, после создания DataSource и, таким образом, DataSet, я использовал drag & drop из дерева DataSource для перетаскивания таблиц и автоматического создания 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);
}
}
Теперь я хочу управлять всеми операциями load/query/insert/update/delete в отдельных потоках. На данный момент я попытался создать новый поток для загрузки данных.
Это я пробовал.
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), и позволяет мне перемещаться по ним.
Нужно ли мне использовать delegates
? Если это так, я думаю, что это будет беспорядок... сколько delegates
я должен создать и для этих методов? Или есть другое решение?
- ОБНОВЛЕНИЕ 1 -
Я знаю, что потоки пользовательского интерфейса автоматически управляются.NET и поэтому программисту не нужно управлять ими с помощью кода. Итак, должна ли быть проблема синхронизации с потоком.NET UI, встроенным в управление? Может быть, мой поток, запущенный Form.Load()
вмешивается в поток пользовательского интерфейса, управляемый.NET?
- ОБНОВЛЕНИЕ 2 -
Я попытался реализовать решение, предложенное faby. Я заменил логику Thread
логикой Task
. Поведение приложения одинаковое, поэтому все, что работает с Thread
, теперь также работает с Task
. Но проблема все еще остается. Поскольку я на.NET 4.0, а не.NET 4.5, я не мог использовать async и ждать. Поэтому я не знаю, будет ли с этим подходом работать пользовательский интерфейс или нет. Любое другое предложение, действительное для.NET 4.0?
Я наконец нашел решение без использования async/wait и других библиотек. Проблема заключалась в том, что я выполнял метод Fill()
TableAdapter
внутри новой Task
поэтому мне нужно было использовать InvokeRequired
для установки источника данных источника привязки в DataTable
в правильном потоке.
Поэтому я использовал delegates
. Я изменил метод, вызванный новой Задачей, и заставил его вызвать 3 других метода (по одному для каждого DataGridView
для заполнения), которые вызывают Fill()
реализующие проверку InvokeRequired
.
Теперь я вижу создание пользовательского интерфейса, а затем через пару секунд асинхронное заполнение DataGridView.
Эта статья была полезной: загрузка данных из TableAdapter async
Спасибо @faby за предложение использовать Task вместо Thread. Это не было решением, но это лучший способ сделать Threading.
Вот окончательный рабочий код.
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
}
вы считаете вариант BackgroundWorker Class?
реализуя DoWork
и ProgressChanged
вы можете делать в DoWork
то, что вы делаете в фоновом потоке, а в ProgressChanged
вы можете обновлять интерфейс
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
для использования async/await с фреймворком 4.0 попробуйте этот NugetPackage (Microsoft.Bcl.Async
)
Threading
, поэтому я начал с класса Thread
потому что я уже использовал его в прошлом. Я действительно не знаю, будет ли BackgroundWorker
лучшим подходом и совместим ли он с моей логикой. Есть также ThreadPool
и TPL
... но в любом случае, переход на BackgroundWorker или что-то еще заставит меня также изменить логику, которую я уже сделал. Вы уверены, что у меня не будет такой же проблемы при использовании BackgroundWorker
?