Search code examples
c#wpfmvvmprismeventaggregator

Cancelling other running tasks queued on Prism EventAggregator


I have a WPF application using Prism. When a user selects rows from the datagrid, it fires a SelectionUpdated event on the EventAggregator.

One of my other ViewModels is listening to that event and when it receives it, kicks off an asynchronous process to retrieve some remote results based on the selection. This can take up to 10 seconds. When it gets the results back, it updates the ViewModel with the results which are then bound to a results grid.

The problem I have is if user makes a second selection while the first is running, ie selects rows 2-4 then changes his mind and selects rows 2-8 instead, it seems the EventAggregator is a single thread pipe and the first received event must complete fully, including the async bit, before the second will begin.

I effectively want a way to cancel the first request if a second one is received. My code-behind for the view is where the selection event is captured, and this code-behind then publishes the SelectionUpdated event. So if the EventAggregator is single thread only, then I need some way in the code-behind to tell my viewmodel that is currently processing the SelectionUpdated event, to cancel and quit out, freeing the EventAggregator subcribe() pipe up to process the second event immediately.

Hope this makes sense. My code behind.

Grid.MouseLeftButtonUp += (sender, args) =>
{
    var selectedEntries = Grid.SelectedCells.Select(c => c.Item as EntryViewModel).Where(c => c != null).Distinct().ToArray();
    _eventAggregator.GetEvent<SelectionUpdatedEvent>().Publish(selectedEntries);
};

And the listening ViewModel (relevant lines only)

_eventAggregator.GetEvent<SelectionUpdatedEvent>().Subscribe(async x => await UpdateSelectedEntries(x));

private async Task UpdateSelectedEntries(EntryViewModel[] selectedEntries)
{
    _selectedEntries = selectedEntries;
    RaisePropertyChanged(nameof(SelectedText));
    RaisePropertyChanged(nameof(SelectedBreakdown));

    await RecalculateAll();
}

private async Task RecalculateAll()
{
    try
    {
        var entryIds = _selectedEntries.SelectMany(s => s.StatusInfo.ValidEntryIds).OrderBy(t => t).ToArray();
        ProcessingCount++;
        var results = await _resultsService.GetResults(_resultType, entryIds);
...
}

It is the final call to GetResults that I want to interrupt if my code-behind detects a new selection has been made, but I cannot figure out how to do this.

Any thoughts?


Solution

  • By default, you subscribe on the publisher thread. You want to subscribe on a threadpool thread so that your subscribtions can run in parallel.

    _eventAggregator.GetEvent<SelectionUpdatedEvent>().Subscribe(async x => await UpdateSelectedEntries(x), ThreadOption.BackgroundThread);
    

    Your subscriber will then cancel the CancellationToken used by the previous task to, replace it with his own (this part obviously has to be synchronized).