1

I'm developing an MDI application in C# with .NET 4.0. Each MDI child will be a form with tabs that contains GroupBoxes with a DataGridView. I implemented a class that is used to manage Threads.

This is the StartNewThread method in my ThreadManager class

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

To create the DataGridViews I used some Wizard component from Oracle Developer Tools for VS library. So, after creating the DataSource and so the DataSet, then I used drag&drop from DataSource tree to drag tables and automatically create DataGridViews.

This is the actual working code, behind the child form, automatically created.

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

What I want to do now is manage all the load/query/insert/update/delete operations on separated threads. For now I tried to create a new Thread to load the data.

This i what I tried.

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
}

It works only for half... There's no errors or exceptions, but if I load data that way, using a different Thread, the DataGridviews doesn't update and I don't see any data when opening the form, even if I move or resize it. Otherwise, using the automatically generated code, the DataGridViews are populated correctly. But, since the wizard also add a navigation bar to the form to navigate through records, I noticed that it works, because it counts the correct number of records and I can use the arrows (first, previous, next, last) to move across records.

Here is an image showing my form. See the navigation bar that is showing the correct number of total records (14) and allows me to navigate through them.

See the navigation bar that is showing the correct number of total records and allows me to navigate through them.

Do I need to use delegates? If so, I think it would be a mess... how many delegates should I create and for those methods? Or is there another solution?

-- UPDATE 1 --

I know that UI threads are automatically managed by .NET and so the programmer don't need to manage them with code. So, should it be a problem of synchronization with the .NET UI thread built in management? Maybe my thread launched by Form.Load() interferes with the UI thread managed by the .NET?

-- UPDATE 2 --

I tried to implement the solution proposed by faby. I replaced my Thread logic with Task logic. The behaviour of the application is the same, so everything that was working with Thread is now working also with Task. But the problem still remains. Since I'm on .NET 4.0 and not .NET 4.5, I could not use async and await. So I don't know if with that approach the UI will work correctly or not. Any other suggestion valid for .NET 4.0?

Cheshire Cat
  • 1,941
  • 6
  • 36
  • 69

2 Answers2

1

do you consider the option of BackgroundWorker Class ?

implementing DoWork and ProgressChanged you can do in DoWork what you are doing in background thread and in ProgressChanged you can update the 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
        }

update 1

another solution could be something like this

public Task LoadDataAsync()
{
    return Task.Factory.StartNew( () =>
    {
        //code to fill your datagridview
    });
}

then

public async Task ChangeUIComponents()
{
    await LoadDataAsync();

    // now here you can refresh your UI elements           
}

update 2

to use async/await with framework 4.0 try with this NugetPackage (Microsoft.Bcl.Async)

faby
  • 7,394
  • 3
  • 27
  • 44
  • I'm not so expert about `Threading`, so I started with `Thread` class because I already used in the past. I don't really know if `BackgroundWorker` would be a better approach and if it is compatible with my logic. There's also `ThreadPool` and `TPL`... but anyway, changing to BackgroundWorker or something else will force me to also change the logic I already made. Are you sure that I'll not have the same problem using `BackgroundWorker`? – Cheshire Cat Dec 03 '14 at 13:28
  • What about my second approach? Does it fit your needs? – faby Dec 03 '14 at 13:55
  • I tried to change my `Thread` logic with `Task` logic. I found those articles: [Tasks](http://dotnetcodr.com/2014/01/01/5-ways-to-start-a-task-in-net-c/) and [Tasks vs Threads](http://stackoverflow.com/questions/13429129/task-vs-thread-differences). It seems that `Task` approach is the modern and easiest way to do threading. The logic is almost the same except that Tasks has no name but instead a unique identifier number auto generated. So, replacing my calls to `ThreadManager.StartNewThread` with `TaskManager.StartNewTask` was easy and works... But my problem still remains with `Task` too!!! – Cheshire Cat Dec 03 '14 at 15:48
  • Also, I cannot use `async` and `await` because **I'm on .NET 4.0** and not 4.5! – Cheshire Cat Dec 03 '14 at 15:51
  • I've updated my answer, try with that package to use async/await with framework 4.0 – faby Dec 03 '14 at 16:00
0

I finally found a solution without using async/await and other libraries. The problem was that I was executing the Fill() method of TableAdapter inside a new Task and so I needed to use InvokeRequired to set the binding source data source to the DataTable within the right thread.

So I used delegates. I changed the method called on the new Task and make it call 3 other methods (one for each DataGridView to fill) that call Fill() implementing the InvokeRequired check.

Now I see the creation of the UI and then, after a couple of seconds, the asynchronous filling of the DataGridViews.

This article was useful: Load data from TableAdapter async

Thanks to @faby for the suggestion to use Task instead of Thread. It was not the solution but it is a better way to do Threading.

Here's the final working code.

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
}

-- Update 1 --

If the methods to call by the new Task are void and without any parameters, you can simplify a bit the above code by using Invoke((MethodInvoker) MethodName). The behaviour of the application is the same.

Here's the simplified version of the code.

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
}
Cheshire Cat
  • 1,941
  • 6
  • 36
  • 69