dalton5
dalton5

Reputation: 935

Task.WhenAll ContinueWith deadlock

I develop my mobile app in xamarin and I send x requests to show the main page of my app.

My code is as below:

    var taskcoll = _mainArticlesAppService.GetWelcomeStartColl().ContinueWith(async (res) =>
        {
            _applicationContext.Categories = res.Result.Categories;
        });

        var taskheros = _mainArticlesAppService.GetWelcomeHerosProfileDto().ContinueWith((res) =>
        {
            _applicationContext.HerosProfileDto = res.Result;
            RaisePropertyChanged(() => HerosProfileDto);
        });

        List<Task> tasksInit = new List<Task>();

        tasksInit.Add(taskcoll);
        tasksInit.Add(taskheros);

        await Task.WhenAll(tasksInit).ConfigureAwait(false);

When there are no issues everything is fine and works.

However when the request can't connect to the remote api server it throws ecxeption in my code and then deadlock I can't do anymore stuff and my screen is frozen.

My code where the deadlock happens is:

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        int count = 0;

       using (var cts = new CancellationTokenSource(new TimeSpan(0, 0, 30)))
        {
            HttpResponseMessage response = null;
            try
            {
                
                 response = await base.SendAsync(request, cts.Token);
            }
            catch(Exception ex)
            {
               
                throw ex; // --> Deadlock
            }
            if (response.StatusCode == HttpStatusCode.Unauthorized &&
                HasBearerAuthorizationHeader(request))
            {
                return await HandleUnauthorizedResponse(request, response, cancellationToken);
            }

            return response;
        }
    }

Upvotes: 1

Views: 328

Answers (1)

aepot
aepot

Reputation: 4824

Don't mix async/await with continuations, it makes the code unpredictable.

Also without continuations the code looks better:

var taskcoll = _mainArticlesAppService.GetWelcomeStartColl(); // start 1st request
var taskheros = _mainArticlesAppService.GetWelcomeHerosProfileDto(); // start 2nd request
// both requests are already running
_applicationContext.Categories = (await taskcoll).Categories; // await 1st request
_applicationContext.HerosProfileDto = await taskheros; // await 2nd one
RaisePropertyChanged(() => HerosProfileDto);

Also try this

response = await base.SendAsync(request, cts.Token).ConfigureAwait(false);

For throw ex ensure that you have the Exception handled with try-catch outside of the request. As you rethrow the Exception as it is and don't do anything else, here inside of SendAsync you don't need a try-catch at all.


Here's an abstract example with multiple async calls that returns data with different delay. With no return type but typed Actions to make a callback. This approach as for me the most accurate to receive the different typed data as soon as possible by completion of each request.

class Program
{
    static async Task Main(string[] args)
    {
        SomeController controller = new SomeController();
        await controller.LoadAllData();
        Console.ReadKey();
    }  
}

public class SomeController
{
    private FirstDataClass _firstProperty;
    private SecondDataClass _secondProperty;
    private ThirdDataClass _thirdProperty;

    public FirstDataClass FirstProperty
    {
        get => _firstProperty;
        set
        {
            _firstProperty = value;
            Console.WriteLine("First data received");
        }
    }

    public SecondDataClass SecondProperty
    {
        get => _secondProperty;
        set
        {
            _secondProperty = value;
            Console.WriteLine("Second data received");
        }
    }
       
    public ThirdDataClass ThirdProperty
    {
        get => _thirdProperty;
        set
        {
            _thirdProperty = value;
            Console.WriteLine("Third data received");
        }
    }

    public async Task LoadAllData()
    {
        List<Task> tasks = new List<Task>();
        tasks.Add(GetFirstData(data => FirstProperty = data));
        tasks.Add(GetSecondData(data => SecondProperty = data));
        tasks.Add(GetThirdData(data => ThirdProperty = data));
        Console.WriteLine("Tasks launched");
        await Task.WhenAll(tasks);
    }

    private async Task GetFirstData(Action<FirstDataClass> action)
    {
        await Task.Delay(1000);
        action(new FirstDataClass());
    }

    private async Task GetSecondData(Action<SecondDataClass> action)
    {
        await Task.Delay(300);
        action(new SecondDataClass());
    }

    private async Task GetThirdData(Action<ThirdDataClass> action)
    {
        await Task.Delay(500);
        action(new ThirdDataClass());
    }
}

public class FirstDataClass { }

public class SecondDataClass { }

public class ThirdDataClass { }

Console output

Tasks launched
Second data received
Third data received
First data received

If you don't want to change the API calling methods, you may use this approach for wrappers of that methods like:

private async Task GetSomeData(Action<SomeType> action)
{
    action(await SomeApiCall());
}

Or some Generic wrapper

private async Task GetDataWrapper<T>(Func<Task<T>> method, Action<T> action)
{
    action(await method);
}

Upvotes: 2

Related Questions