如何处理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; } }
问题原因
- AX服务端默认不监听客户端中止请求:客户端调用
Abort仅断开网络连接,服务端进程不会收到终止信号,仍会执行完整个操作。 - 测试代码中的
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
相关产品推荐
相关产品推荐

