Search code examples
c#wpfasync-awaitdeadlockcancellation-token

Waiting for cancelled async method from UI thread?


I have a WPF program appending to a textbox asynchronously. If the user selects a new provider via combobox, the appending should be cancelled and called again with the new provider. problem is the updateMoviesTask?.Wait() deadlocks. I guess it is because UpdateMovies() waits for ui thread and ProviderId property waits for the task. How can I solve this?

CancellationTokenSource source;
Task updateMoviesTask;

async Task UpdateMovies()
{
    source?.Dispose();
    source = new CancellationTokenSource();

    CancellationToken cancel = source.Token;

    var request = new GetPopularRequest(Country.Germany);

    for (int i = 1; i <= TotalPages; i++)
    {
        request.Page = i;
        request.ProviderId = providerId;

        var getPopularResponse = await justWatchClient.GetPopularAsync(request);

        // do ui stuff here        

        if (cancel.IsCancellationRequested)
            return;
    }
}

public GetProviderResponse ProviderId
{
    get { return providerId; }
    set
    {
        this.providerId = value; 
        this.source?.Cancel();
        updateMoviesTask?.Wait();
        updateMoviesTask = this.UpdateMovies();
        this.OnPropertyChanged("");
    }
}

Solution

  • If I understand correctly, then you need to - Look at this version of the code (without taking into account the handling of possible exceptions):

        private CancellationTokenSource source = new();
        private readonly object locker = new();
    
        private void UpdateMovies() => Task.Run(() =>
        {
            source.Dispose();
            source = new CancellationTokenSource();
            CancellationToken cancel = source.Token;
    
            lock (locker)
            {
                var request = new GetPopularRequest(Country.Germany);
    
                for (int i = 1; i <= TotalPages; i++)
                {
                    request.Page = i;
                    request.ProviderId = providerId;
    
                    // Need a synchronous version of the method
                    // var getPopularResponse = await justWatchClient.GetPopularAsync(request);
                    var getPopularResponse = justWatchClient.GetPopular(request);
    
                    // do ui stuff here        
    
                    if (cancel.IsCancellationRequested)
                        return;
                }
            }
        });
    
        public GetProviderResponse ProviderId
        {
            get { return providerId; }
            set
            {
                this.providerId = value;
                UpdateMovies();
                this.OnPropertyChanged(string.Empty);
            }
        }