0

I'm struggling to understand what is going on with this code. I've done async/await for a while now but haven't stumbled on this issue before (unless I'm just not seeing what I've done)

I'm loading a CSV file into a DataTable then I want to pull the list of column names from the DataTable to manipulate later.

In the first method, I load the CSV async then assign the DataGrid to view the data. I wasn't able to use the _ColumnList = ... in the first method because for whatever reason, the "_dtSampleCSV.Columns" did not resolve; it's not seeing that it's a DataTable.

So I moved that line to another method where it pulls the column names and assigns those names to another DataGrid. When it hits this method, there's no data in the _dtSampleCSV DataTable (no rows, no columns), yet the first DataGrid does populate with data.

I've always assumed that using an "await" waits for the thread to complete before proceeding. So either that's not true, or the thread did complete but the DataTable wasn't assigned yet.

I can only assume the data doesn't exist at that point, but the DataGrid's source is set so when the data is populated; it shows. But how do I prevent execution if await isn't completing the dataload into the DataTable?

private DataTable _dtSampleCSV = new DataTable();
private List<string> _ColumnList = new List<string>();

private async void btnAdd_Click(object sender, RoutedEventArgs e)
{
    spNew.Visibility = Visibility.Visible;

    OpenFileDialog openFileDialog = new OpenFileDialog();
    if (openFileDialog.ShowDialog() == true)
    {
        tbFilePath.Text = openFileDialog.FileName;
        var _dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);
            
        dgCSVExample.DataContext = _dtSampleCSV;
        dgCSVExample.Visibility = Visibility.Visible;

        SetColumnGrid();

    }
}

private void SetColumnGrid()
{
    _ColumnList = (from DataColumn dc in _dtSampleCSV.Columns.Cast<DataColumn>() select dc.ColumnName).ToList();
    dgColumnNames.DataContext = _ColumnList;  // breakpoint set here to look at _dtSampleCSV and no data exists at runtime
    dgColumnNames.Visibility = Visibility.Visible;
}

// csv method
public static async Task<DataTable> LoadCSVAsync(string filePath)
{
    using (var reader = File.OpenText(filePath))
    {
        var fileText = await reader.ReadToEndAsync();
        using (TextReader sr = new StringReader(fileText))
        {
            var adapter = new GenericParsing.GenericParserAdapter(sr);
            adapter.FirstRowHasHeader = true;
            adapter.MaxBufferSize = 4096;
            adapter.MaxRows = 3;
            DataTable dtProcess = adapter.GetDataTable();
            return dtProcess;
        }
    }
}
Zonus
  • 2,313
  • 2
  • 26
  • 48
  • 1
    You've declared a *local variable* in your `btnAdd_Click` method - `var _dtSampleCSV = ...`. So you're never actually assigning a new value to the *field* which is used in `SetColumnGrid`. Just remove the `var` part. – Jon Skeet Jan 21 '22 at 17:31

1 Answers1

1

Your LoadCSVAsync method is static, which explains why it can't access the _dtSampleCSV instance field.

Your btnAdd_Click method creates a local variable called _dtSampleCSV, assigns that to the dgCSVExample.DataContext, and then throws it away. Data from that local variable will not be available in the _dtSampleCSV field.

Change your method so that it assigns the data to the field, rather than creating a local variable:

// Remove this:
// var _dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);

// Use this:
_dtSampleCSV = await LoadCSVAsync(openFileDialog.FileName);

NB: You should try to avoid async void methods wherever possible.

Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
  • doh. Bingo! That's what I get for coding at 3am and coming back to it. I couldn't see the trees through the forest. Thanks; that was it! – Zonus Jan 21 '22 at 17:32
  • 4
    *"You should try to avoid async void methods wherever possible."* -- Not when `async void` is used for its [intended purpose](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void), which is specifically to make asynchronous event handlers possible. – Theodor Zoulias Jan 21 '22 at 17:39
  • BTW, the only place I use void on async/await is on event handlers like a button. I did this based on a few places I read about that a few years ago including this one: https://www.pluralsight.com/guides/returning-void-from-c-async-method – Zonus Jan 21 '22 at 17:39
  • @TheodorZoulias From that article: *"An approach I like to take is to minimize the code in my asynchronous event handler—for example, have it await an async Task method that contains the actual logic."* – Richard Deeming Jan 24 '22 at 08:40
  • Richard the quoted text is about refactoring the code for testability. The `async void` event handler is still there. – Theodor Zoulias Jan 24 '22 at 16:01
  • @TheodorZoulias It's still there, but it should be doing as little as possible - hence the "wherever possible" comment. Once you've moved the bulk of the code to an `async Task` method, you can even get rid of the `async void` by not using `await`: `public void button_Click(object sender, EventArgs e) { _ = TheRealAsyncMethod(); }` – Richard Deeming Jan 24 '22 at 16:06
  • Richard saying *"You should try to avoid `async void` methods wherever possible."* is not the same with *"You should try to minimize the code inside `async void` event handlers, in order to be able to test this code."* Also I am not a fan of [fire-and-forget](https://stackoverflow.com/questions/61316504/proper-way-to-start-and-async-fire-and-forget-call/61320933#61320933), which is what you do with the discard-using suggestion. – Theodor Zoulias Jan 24 '22 at 16:11