【问题标题】:Task.WhenAll ContinueWith deadlockTask.WhenAll ContinueWith 死锁
【发布时间】:2020-08-23 10:34:45
【问题描述】:

我在 xamarin 中开发我的移动应用程序并发送 x 个请求以显示我的应用程序的主页。

我的代码如下:

    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);

当没有问题时,一切都很好。

但是,当请求无法连接到远程 api 服务器时,它会在我的代码中引发 ecxeption,然后死锁,我无法再做任何事情并且我的屏幕被冻结。

我发生死锁的代码是:

 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;
        }
    }

【问题讨论】:

    标签: c# xamarin async-await task deadlock


    【解决方案1】:

    不要将async/await 与延续混合,这会使代码变得不可预测。

    同样没有延续代码看起来更好:

    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);
    

    也试试这个

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

    对于throw ex,确保在请求之外使用try-catch 处理Exception。当您按原样重新抛出 Exception 并且不执行任何其他操作时,在 SendAsync 内部,您根本不需要 try-catch


    这是一个带有多个异步调用的抽象示例,这些调用以不同的延迟返回数据。没有返回类型,但输入了 Actions 来进行回调。这种方法对我来说最准确,可以通过完成每个请求尽快接收到不同类型的数据。

    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 { }
    

    控制台输出

    Tasks launched
    Second data received
    Third data received
    First data received
    

    如果您不想更改 API 调用方法,您可以将此方法用于该方法的包装器,例如:

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

    或一些通用包装器

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

    【讨论】:

    • 谢谢。我应该在哪里调用 Task.WhenAll?在任务英雄之后?如果我这样做,我将不得不等待所有任务完成才能显示结果。正确的?我执行多个请求的目标是在请求完成后在我的手机屏幕上显示检索到的结果。即使其他人不是。
    • @dalton5 你在这里不需要Task.WhenAllawaits 都已经内联了。
    • @dalton5 为了在没有复杂转换的情况下实现这一点,所有请求都应该具有相同的返回类型。收到请求后立即更新数据Task.WhenAll 不适合您,但Task.WhenAny 适合。
    • @dalton5 不,有两个await 来电,一切正常。 :)
    • 谢谢。它更清楚。我正在与 WhenAny 一起研究解决方案,并了解如何改进。如果可行,将发布我的解决方案。谢谢
    猜你喜欢
    • 2022-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 1970-01-01
    • 2020-08-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多