using System; using System.Threading.Tasks; namespace ZB.MOM.WW.LmxOpcUa.Host.Utilities { /// /// Bounded safety wrappers for blocking on async tasks from synchronous OPC UA stack /// callbacks (Read, Write, HistoryRead*, BuildAddressSpace). These are backstops: the /// underlying MxAccess / Historian clients already enforce inner timeouts on the async /// path, but an outer bound is still required so the stack thread cannot be parked /// indefinitely by a hung scheduler, a slow reconnect, or any other non-returning /// async path. /// /// /// On timeout, the underlying task is NOT cancelled — it runs to completion on the /// thread pool and is abandoned. Callers must be comfortable with the fire-forget /// semantics of the background continuation. This is acceptable for the current call /// sites because MxAccess and Historian clients are shared singletons whose background /// work does not capture request-scoped state. /// internal static class SyncOverAsync { public static void WaitSync(Task task, TimeSpan timeout, string operation) { if (task == null) throw new ArgumentNullException(nameof(task)); try { if (!task.Wait(timeout)) throw new TimeoutException($"{operation} exceeded {timeout.TotalSeconds:0.#}s"); } catch (AggregateException ae) when (ae.InnerExceptions.Count == 1) { // Unwrap the single inner exception so callers can write natural catch blocks. throw ae.InnerExceptions[0]; } } public static T WaitSync(Task task, TimeSpan timeout, string operation) { if (task == null) throw new ArgumentNullException(nameof(task)); try { if (!task.Wait(timeout)) throw new TimeoutException($"{operation} exceeded {timeout.TotalSeconds:0.#}s"); return task.Result; } catch (AggregateException ae) when (ae.InnerExceptions.Count == 1) { throw ae.InnerExceptions[0]; } } } }