You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity UWP第三次API调用触发TaskCancellationException问题求助

Unity UWP 第三次API调用固定抛出TaskCanceledException 排查方案

问题现象

  • 运行环境:Unity UWP平台
  • 异常表现:顺序调用3个接口时,无论如何调整调用顺序,固定第三次调用抛出TaskCanceledException;仅调用1个或2个接口时逻辑运行正常,后端API服务本身无故障。
  • 原实现代码:
public class DashboardManager : MonoBehaviour
{
    public TMP_Text Status;

    async Task Awake()
    {
        await GetPerformanceInfo().ConfigureAwait(false);
        await GetProductionInfo().ConfigureAwait(false);
        await GetChecklistInfo().ConfigureAwait(false);
    }


    private async Task GetChecklistInfo()
    {
        try
        {
            HttpClient httpClient = new HttpClient((HttpMessageHandler)ApiHelpers.HttpClientHandler);
            httpClient.BaseAddress = new Uri(ApiHelpers.PlantApiBaseAddress.CheckBaseUrl(true));

            var plantApiClient = new PlantApiClient(httpClient, default);
            var checklists = await plantApiClient.GetChecklistWidgetInfo(UserSettings.Location);
        }
        catch (LogoutException ex)
        {
            SceneManager.LoadScene("LoginScene");
        }
        catch (TaskCanceledException ex)
        {
            Status.text = "Checklist: " + ex.Message;
        }
        catch (Exception ex)
        {
            Status.text = "Checklist: " + ex.Message;
        }
    }

    public void OnClickButton(string guid)
    {
        GlobalManager.CurrentChecklistID = guid;
        SceneManager.LoadScene("ChecklistsScene");
    }

    private async Task GetPerformanceInfo()
    {
        try
        {
            HttpClient httpClient = new HttpClient((HttpMessageHandler)ApiHelpers.HttpClientHandler);
            httpClient.BaseAddress = new Uri(ApiHelpers.PlantApiBaseAddress.CheckBaseUrl(true));

            var plantApiClient = new PlantApiClient(httpClient, default);
            var perfInfo = await plantApiClient.GetPerformanceWidgetInfo(UserSettings.Location);
        }
        catch (LogoutException ex)
        {
            SceneManager.LoadScene("LoginScene");
        }
        catch (TaskCanceledException ex)
        {
            Status.text = "Performance: " + ex.Message;
        }
        catch (Exception ex)
        {
            Status.text = "Performance: " + ex.Message;
        }
    }

    private async Task GetProductionInfo()
    {
        try
        {
            HttpClient httpClient = new HttpClient((HttpMessageHandler)ApiHelpers.HttpClientHandler);
            httpClient.BaseAddress = new Uri(ApiHelpers.PlantApiBaseAddress.CheckBaseUrl(true));

            var plantApiClient = new PlantApiClient(httpClient, default);
            var prodInfo = await plantApiClient.GetProductionWidgetInfo(UserSettings.Location);
        }
        catch (LogoutException ex)
        {
            SceneManager.LoadScene("LoginScene");
        }
        catch (TaskCanceledException ex)
        {
            Status.text = "Production: " + ex.Message;
        }
        catch (Exception ex)
        {
            Status.text = "Production: " + ex.Message;
        }
    }
}

根因定位

这个异常是两个UWP/Unity异步机制的限制共同触发的:

  • UWP HttpClient默认连接数限制:UWP平台原生HttpClientHandler默认对同域名服务的最大并发连接数为2,代码每次调用接口都新建HttpClient但复用同一个底层Handler,前两个请求占用连接后未及时释放资源,第三个请求等待连接超时,就会抛出TaskCanceledException——HttpClient的超时异常统一包装为这个异常类型,和主动取消任务抛出的异常类型一致。
  • Unity生命周期异步写法错误:Unity不会主动跟踪async Task签名的生命周期方法(比如Awake/Start)返回的Task,加上代码用了ConfigureAwait(false)让await之后的逻辑跑在线程池,脱离了Unity主线程上下文,一方面会让非主线程访问UI组件(比如给Status.text赋值)触发隐式异常,另一方面Unity对象生命周期结束时会直接取消未被跟踪的Task,放大了连接超时的问题。

修复步骤

按以下顺序修改即可解决问题:

  1. 调整生命周期异步写法
    把Awake方法签名改成async void Awake(),Unity原生对生命周期方法的async void签名有正确的跟踪支持;去掉所有接口调用的ConfigureAwait(false),保证await之后逻辑回到主线程执行,避免跨线程访问Unity对象:
    async void Awake()
    {
        await GetPerformanceInfo();
        await GetProductionInfo();
        await GetChecklistInfo();
    }
    
  2. 修复HttpClient连接池限制
    初始化ApiHelpers.HttpClientHandler时显式设置同域名最大并发连接数,覆盖UWP默认的2连接限制:
    // 在全局初始化逻辑中配置Handler
    var httpHandler = new HttpClientHandler();
    // 根据业务需求设置并发数,设置为10足够覆盖常规多接口调用场景
    httpHandler.MaxConnectionsPerServer = 10;
    ApiHelpers.HttpClientHandler = httpHandler;
    
    额外优化:不要每次调用接口都新建HttpClient实例,全局复用同一个HttpClient即可,减少资源释放不及时的问题。
  3. 异常快速校验
    捕获TaskCanceledException时可以加日志判断异常来源:如果ex.CancellationToken.IsCancellationRequested为false,就可以确认是请求超时/连接不足导致的异常,而非代码主动触发的取消,方便后续排查。

内容的提问来源于stack exchange,提问作者Alain Cuypers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:42:16