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

如何处理AX服务超时后仍运行问题?中止操作无效

AX服务操作无法从.NET客户端中止的解决方案

问题背景

自定义AX service operation耗时可达5分钟以上,从.NET应用调用Abort操作完全无效:客户端中止后,AX服务仍会持续运行至完成,导致无法及时获知操作结果(比如长时间过账的成功/失败状态)。

测试用AX X++服务代码:

[SysEntryPointAttribute(true)]
public str callAXTimeDelay(int _sleepSeconds)
{
    Table1                  table1;

    ttsBegin;
    table1.clear();
    table1.SleepData = strFmt("STARTED: %1", DateTimeUtil::utcNow());
    table1.insert();
    ttsCommit;
    
    sleep(_sleepSeconds * 1000);
    
    ttsBegin;
    table1.clear();
    table1.SleepData = strFmt("COMPLETED: %1", DateTimeUtil::utcNow());
    table1.insert();
    ttsCommit;
    
    return strFmt("COMPLETED: %1", DateTimeUtil::utcNow());
}

.NET客户端测试代码:

internal class Program
{
    static void Main(string[] args)
    {
        new Program().Run();

        Console.WriteLine("Ended, press any key to exit...");
        Console.ReadKey();
    }

    public void Run()
    {
        AXServicesClient axClient15Sec = new AXServicesClient();
        AXServicesClient axClient5sec = new AXServicesClient();

        var job15sec = DoLongRunningCall(axClient15Sec, 15);
        var job5sec = DoLongRunningCall(axClient5sec, 5);

        try
        {
            var result = Task.Run(() => Task.WhenAny(job15sec, job5sec)).GetAwaiter().GetResult();

            if (result == job15sec)
            {
                Console.WriteLine("job15sec finished first, aborting job5sec");
                axClient5sec.Abort();
            }
            else if (result == job5sec)
            {
                // 5秒任务完成后尝试中止15秒任务,但服务端仍会插入完成记录
                Console.WriteLine("job5sec finished first, aborting job15sec");
                axClient15Sec.Abort();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception: " + e.Message);

            axClient15Sec.Abort();
            axClient5sec.Abort();
        }
        axClient15Sec.Close();
        axClient5sec.Close();
    }

    public async Task<string> DoLongRunningCall(AXServicesClient client, int seconds)
    {
        var result = await client.callAXTimeDelay(new CallContext
        {
            Company = "ABCD",
            Language = "en-us"
        }, seconds);

        return result.response;
    }
}

问题原因

  1. AX服务端默认不监听客户端中止请求:客户端调用Abort仅断开网络连接,服务端进程不会收到终止信号,仍会执行完整个操作。
  2. 测试代码中的sleep是阻塞式操作,期间无法响应任何中断信号;且X++代码未添加中断检查逻辑。

解决方法

1. 修改AX X++服务代码,添加中断检查逻辑

将阻塞式sleep拆分为多次短时间睡眠,每次睡眠后检查线程是否被中断;在关键业务节点(事务提交前、循环迭代中)也需添加检查,一旦检测到中断则抛出异常并回滚未提交事务。

修改后的X++代码示例:

[SysEntryPointAttribute(true)]
public str callAXTimeDelay(int _sleepSeconds)
{
    Table1                  table1;
    int                     elapsedSeconds = 0;
    const int               checkInterval = 1; // 每1秒检查一次中断

    ttsBegin;
    table1.clear();
    table1.SleepData = strFmt("STARTED: %1", DateTimeUtil::utcNow());
    table1.insert();
    ttsCommit;
    
    // 替换阻塞sleep为循环短睡眠+中断检查
    while (elapsedSeconds < _sleepSeconds)
    {
        // 检查线程是否被中断
        if (Thread::interrupted())
        {
            ttsBegin;
            table1.clear();
            table1.SleepData = strFmt("ABORTED: %1", DateTimeUtil::utcNow());
            table1.insert();
            ttsCommit;
            
            throw error("Operation was aborted by client.");
        }
        
        sleep(checkInterval * 1000);
        elapsedSeconds += checkInterval;
    }
    
    // 完成前再次检查中断
    if (Thread::interrupted())
    {
        ttsBegin;
        table1.clear();
        table1.SleepData = strFmt("ABORTED BEFORE COMPLETION: %1", DateTimeUtil::utcNow());
        table1.insert();
        ttsCommit;
        
        throw error("Operation was aborted by client.");
    }
    
    ttsBegin;
    table1.clear();
    table1.SleepData = strFmt("COMPLETED: %1", DateTimeUtil::utcNow());
    table1.insert();
    ttsCommit;
    
    return strFmt("COMPLETED: %1", DateTimeUtil::utcNow());
}

2. .NET客户端优化中断逻辑

使用CancellationToken配合服务调用,确保中断信号正确传递;同时处理服务端抛出的中断异常,及时释放客户端资源:

修改后的.NET代码片段:

public async Task<string> DoLongRunningCall(AXServicesClient client, int seconds, CancellationToken cancellationToken)
{
    // 注册取消回调,触发服务中止
    using (cancellationToken.Register(() => client.Abort()))
    {
        try
        {
            var result = await client.callAXTimeDelay(new CallContext
            {
                Company = "ABCD",
                Language = "en-us"
            }, seconds);

            return result.response;
        }
        catch (CommunicationException ex)
        {
            // 捕获服务中止导致的通信异常
            Console.WriteLine("Operation aborted: " + ex.Message);
            throw;
        }
    }
}

// 在Run方法中使用CancellationTokenSource
public void Run()
{
    using var cts15 = new CancellationTokenSource();
    using var cts5 = new CancellationTokenSource();
    
    AXServicesClient axClient15Sec = new AXServicesClient();
    AXServicesClient axClient5sec = new AXServicesClient();

    var job15sec = DoLongRunningCall(axClient15Sec, 15, cts15.Token);
    var job5sec = DoLongRunningCall(axClient5sec, 5, cts5.Token);

    try
    {
        var result = Task.Run(() => Task.WhenAny(job15sec, job5sec)).GetAwaiter().GetResult();

        if (result == job15sec)
        {
            Console.WriteLine("job15sec finished first, aborting job5sec");
            cts5.Cancel();
        }
        else if (result == job5sec)
        {
            Console.WriteLine("job5sec finished first, aborting job15sec");
            cts15.Cancel();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception: " + e.Message);
        cts15.Cancel();
        cts5.Cancel();
    }
    finally
    {
        axClient15Sec.Close();
        axClient5sec.Close();
    }
}

3. 额外注意事项

  • 涉及长事务的操作,建议拆分为多个小事务,避免中断时回滚大量数据。
  • 若使用SysOperationFramework开发服务,可直接利用框架的中断支持,无需手动实现线程检查。

内容的提问来源于stack exchange,提问作者William YK

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 10:50:23