0

I'm writing a service that charts some data. The data is retrieved from blob storage by a client that only has an async Get method to get the data. Once the data is retrieved it is used to populate the chart and this is done using another method Redraw that is also async. I would like the chart to update every minute or so so I have the Get and Redraw called inside a while loop with a delay.

private async Task Redraw(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        var data = await DataProvider.Get();

        await Chart.Redraw(data.Labels, new[] {data.DataSet});

        await Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
    }
}

Unfortunately, this fails. I get a NullReferenceException and a AggregateException from somewhere in the chart library. Modifying the code as follows does work.

private async Task Redraw(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        var data = Task.Run(() => DataProvider.Get()).GetAwaiter().GetResult();

        await Task.WhenAll(Chart.Redraw(data.Labels, new[] {data.DataSet}));

        await Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
    }
}

I'm not sure why the first version using async/await fails. I believe that it's because the Redraw is trying to run before the data task is finished but I also believed that awaiting the Get method would mean that Redraw would not be called until the data task was complete. When I debug those lines the data does appear to be populated before the next line is executed so i'm a bit confused.

Exception thrown: 'System.NullReferenceException' in Blazorise.Charts.dll Exception thrown: 'System.NullReferenceException' in System.Private.CoreLib.dll 'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.1\System.Diagnostics.StackTrace.dll'. 'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.1\System.Reflection.Metadata.dll'. 'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.1\System.Collections.Immutable.dll'. Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: Object reference not set to an instance of an object.

System.NullReferenceException: Object reference not set to an instance of an object. at Blazorise.Charts.JS.ToChartDataSet[T](ChartData1 data) at Blazorise.Charts.JS.InitializeChart[TItem,TOptions](IJSRuntime runtime, DotNetObjectReference1 dotNetObjectReference, Boolean hasClickEvent, Boolean hasHoverEvent, String canvasId, ChartType type, ChartData1 data, TOptions options, String dataJsonString, String optionsJsonString) at Blazorise.Charts.BaseChart4.Initialize()
at Blazorise.Charts.BaseChart`4.OnAfterRenderAsync(Boolean firstRender) Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit 'v_kcV7GT8jgmXOLCuNjM_i918pfAqsaPh5KVrAsZNUw'.

System.AggregateException: One or more errors occurred. (Object reference not set to an instance of an object.) ---> System.NullReferenceException: Object reference not set to an instance of an object. at Blazorise.Charts.JS.ToChartDataSet[T](ChartData1 data) at Blazorise.Charts.JS.InitializeChart[TItem,TOptions](IJSRuntime runtime, DotNetObjectReference1 dotNetObjectReference, Boolean hasClickEvent, Boolean hasHoverEvent, String canvasId, ChartType type, ChartData1 data, TOptions options, String dataJsonString, String optionsJsonString) at Blazorise.Charts.BaseChart4.Initialize()
at Blazorise.Charts.BaseChart`4.OnAfterRenderAsync(Boolean firstRender) --- End of inner exception stack trace ---

I'm digging deeper and I don't think the null reference is being throw when calling Chart.Redraw at all. When this line executes the data is not null. Instead it looks like it is related somehow to async/await. The call that precedes Redraw looks like this..

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!_isAlreadyInitialized && firstRender)
    {
        _isAlreadyInitialized = true;

        await Redraw(_cancellationToken);
    }
}

This overrides the method of the base class from the library. The base class implementation is as follows..

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    await this.Initialize();
  else
    await this.Update();
}

It is this call to Initialize which is calling another method..

private static object ToChartDataSet<T>(ChartData<T> data)
{
  return (object) new
  {
    Labels = data.Labels,
    Datasets = ((IEnumerable<object>) data.Datasets).ToList<object>()
  };
}

..and it's here where the data is null.

For some reason using await/async in the Redraw method results in the base class calling Initialize => ToChartDataSet before the call to DataProvider.Get is complete. When using GetAwaiter().GetResult() then Initialize is not called until the DataProvider.Get has completed.

Chi
  • 105
  • 3
  • 13
  • 1
    Does this answer your question? [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Sir Rufo Feb 29 '20 at 13:30
  • 3
    "I believe that it's because the Redraw is trying to run before the data task is finished" - no, the await must have completed to get to the redraw call, so that shouldn't be the cause. This could perhaps be a subtle sync-context thing; is there a stack trace? – Marc Gravell Feb 29 '20 at 13:37
  • @MarcGravell I'll add the trace – Chi Feb 29 '20 at 13:51
  • 1
    The error is triggered by passing `null` as an argument to `Chart.Redraw`. It looks like `data.Labels` returns `null`. Can you reproduce this or is this a random error? In debug mode check the value of `data.Label`. If it's `null`, you have to find the reason, why the `Label` property is not set to a value. This has nothing to do with async/await. – BionicCode Feb 29 '20 at 14:07
  • `Label` is not `null` when I debug it - it's not random either, it happens every time. @BionicCode – Chi Feb 29 '20 at 14:17
  • 1
    Please check the value of `data.DataSet`. – BionicCode Feb 29 '20 at 14:32
  • Both `data.Label` and `data.DataSet` are not null. – Chi Feb 29 '20 at 14:36
  • Have you tried to call `base.OnAfterRenderAsync(firstRender)` from inside your override _before_ you continue to execute your custom code? – BionicCode Feb 29 '20 at 15:00
  • I can give it a try ;) – Chi Feb 29 '20 at 15:02
  • It didn't work to call the base first - firstly because what I thought was the base class was not the base class but another class that inherited from the same base class. In the end it was not the `Chart.Redraw` method that was triggering the error. It was a call to `Initialize` in a different `BaseChart` class. Providing a default instance of `ChartData` in the index.razor component fixed it. – Chi Feb 29 '20 at 18:39

0 Answers0