0

I am working on a WPF Prism application. I have a DelgateCommand that is responsible for populating an ObservableCollection which is owned by the UI thread asynchronously using async and await. The collection in turn is bound to a chart.

In order to enable the collection to be accessed by multiple threads, I have enabled the synchronization which is as follows:

BindingOperations.EnableCollectionSynchronization(ChartBoundCollection, _lock);

The command handler logic is as as follows:

    private async void ShowPatientVisitsVsDays()
        {
          IsChartBeingPopulated = true;
          this.ChartSubTitle = "Requests Vs Days";
          this.SeriesTitle = "Days";
          ChartBoundCollection.Clear();
          await ShowRequestsVsDaysAsync();
          IsChartBeingPopulated = false;
        }

The Logic which populates the observable collection is as follows:

private async Task ShowRequestsVsDaysAsync()
    {
        await Task.Run(() =>
            {
                if (PatientVisits.Count() > 0)
                {
                    var days = PatientVisits.Select(p => p.VisitDate.Value.Date).Distinct();
                    foreach (var i in days)
                    {

                        var dayVisitCount = PatientVisits.Where(p => p.VisitDate.Value.Date == i).Count();
                        ChartBoundCollection.Add(new PatientVisitVsXAxis() { XAxis = i.ToShortDateString(), NumberOfPatientVisits = dayVisitCount });
                    }
                }
            });
    }

The issue that I am facing is that the continuation where I am setting IsChartBeingPopulated = false is not getting executed after the asynchronous method on which the await is set is completed.

await ShowRequestsVsDaysAsync();

Thus IsChartBeingPopulated is set even before the asynchronous method is completed.

the command handler ShowPatientVisitsVsDays() is invoked by the click of the button on the View. The button is bound to the following command:

ShowPatientVisitsVsDaysCommand = new DelegateCommand(ShowPatientVisitsVsDays);

IsChartBeingPopulated is being used to set the IsBusy DependencyProperty of the BusyIndiator control belonging to the 'Extended WPF ToolKit'. The idea is to show the BusyIndicator while the chart data is being populated in the bound collection.

<xctk:BusyIndicator IsBusy="{Binding Path=IsChartBeingPopulated}" >
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
            </Grid.RowDefinitions>
            <chart:ClusteredColumnChart Grid.Row="0" ChartTitle="Patient Visits History" ChartSubTitle="{Binding Path=ChartSubTitle}">
                <chart:ClusteredColumnChart.Series>
                    <chart:ChartSeries SeriesTitle="{Binding Path=SeriesTitle}" DisplayMember="XAxis" ValueMember="NumberOfPatientVisits" ItemsSource="{Binding Path=ChartBoundCollection}" />
                </chart:ClusteredColumnChart.Series>
            </chart:ClusteredColumnChart>
        </Grid>
    </xctk:BusyIndicator>

Not sure what the issue is. Does someone has any idea what is causing this?

Eldho
  • 7,795
  • 5
  • 40
  • 77
  • 2
    Shouldn't you change `ShowPatientVisitsVsDays` type to Task, and await for `ShowPatientVisitsVsDays` call, to flatten out that async state? – Tomas Smagurauskas Jan 11 '16 at 09:07
  • Thanks for your comment Tomas. I cannot change the return type for ShowPatientVisitsVsDays, since it is a command handler. A similar situation would be handling a click event of a button asynchronously, in this case too you cannot change the signature of the event handler to return task. – lovecoding Jan 11 '16 at 09:11
  • @lovecoding: do you want to tell, that `ShowRequestsVsDaysAsync` call is not awaited? Besides, since `ShowRequestsVsDaysAsync` contains no asynchronous code, you should make it synchronous and wrap into `Task.Run` in `ShowPatientVisitsVsDays`. – Dennis Jan 11 '16 at 09:47

2 Answers2

2

Your are not synchronizing the access to the collection. BindingOperations.EnableCollectionSynchronization does not magically make collection thread safe. It only ensures that databinding engine does not enumerate the collection without taking the lock.

You still need to take the lock on _lock object when adding and clearing collection.

See here for more info on EnableCollectionSynchronization.

Community
  • 1
  • 1
ghord
  • 13,260
  • 6
  • 44
  • 69
0

You can do something like this;

    private async void ShowPatientVisitsVsDays()
    {
        IsChartBeingPopulated = true;
        this.ChartSubTitle = "Requests Vs Days";
        this.SeriesTitle = "Days";
        new ChartBoundCollection().Clear();
        IsChartBeingPopulated = await ShowRequestsVsDaysAsync();//here we are waiting till the async method is finished.
    }


    private async Task<bool> ShowRequestsVsDaysAsync()
    {
       return await Task.Run(() =>
        {
            if (PatientVisits.Any())//replace Count with Any to avoid unwanted enumerations.
            {
                var days = PatientVisits.Select(p => p.VisitDate.Value.Date).Distinct();
                foreach (var i in days)
                {

                    var dayVisitCount = PatientVisits.Count(p => p.VisitDate.Value.Date == i);
                    chartBoundCollection.Add(new PatientVisitVsXAxis() { XAxis = i.ToShortDateString(), NumberOfPatientVisits = dayVisitCount });
                }
            }
            Thread.Sleep(5000);//this is for testing. Sleep the thread for 5secs. Now your busyIndicator must be visible for 5secs minimum.
            return false;//return false, so that we can use it to populate IsChartBeingPopulated 
        });
    }

UPDATE: I think you have some doubts about async await. Below code will help you to clear them up.

Create a new console app and place this code in Program class.

As per your comment below, the text in ShowPatientVisitsVsDays should get printed before it prints anything from the 'ShowRequestsVsDaysAsync' method. Does it work that way? Test this and let us know.

   static void Main(string[] args)
    {
        Console.WriteLine("Main started");
        ShowPatientVisitsVsDays();
        Console.ReadLine();
    }

   private static async void ShowPatientVisitsVsDays()
    {
        await ShowRequestsVsDaysAsync();            
        Console.WriteLine("ShowPatientVisitsVsDays() method is going to SLEEP");
        Thread.Sleep(2000);
        Console.WriteLine("ShowPatientVisitsVsDays() method finished");
    }

   private static async Task ShowRequestsVsDaysAsync()
    {
        await Task.Run(() =>
        {
            Console.WriteLine("ASYNC ShowRequestsVsDaysAsync() is going to SLEEP.");
            Thread.Sleep(5000);
            Console.WriteLine("ASYNC ShowRequestsVsDaysAsync finished.");
        });
    }
 }
Kosala W
  • 2,133
  • 1
  • 15
  • 20
  • @Kosala: I have tried what you have suggested, but strangely the wait does not happen until the async method is completed. – lovecoding Jan 11 '16 at 09:57
  • Can you show us how you call `ShowPatientVisitsVsDays` and the usage of `IsChartBeingPopulated`? – Kosala W Jan 11 '16 at 10:08
  • I tested a similar code with Busy indicator and it is working. I have added `Thread.Sleep(5000);` to the code. Now your busy status must be shown at least for 5 secs. – Kosala W Jan 11 '16 at 11:48
  • Just noticed you have used ObservableCollection for `ChartBoundCollection`. What's the reason for it? It notifies UI every time you add a new item to the collection inside your async method. Is that what you want or should it refresh only after adding all values? If so change it to a List. – Kosala W Jan 11 '16 at 11:56
  • Agreed, adding Thread.Sleep, does shows up the busy indicator. Also I would be needing the ObservableCollection bounded to the Chart, since the contents of the collection would be modified at run time based on the click of context menu Items on the chart. The issue which is bothering me is that the await is not working as expected. Ideally it should let the asynchronous method complete and then execute the continuation i.e. the line of code after the await which sets the IsChartBeingPopulated to false. – lovecoding Jan 11 '16 at 18:07
  • Which means this code solved your original problem of _How to populate `IsChartBeingPopulated` after the async method is finished._ I have updated my answer with an example that will help you to understand the behaviour of Async await. – Kosala W Jan 12 '16 at 00:01