C#中如何实现专用线程初始化并调用静态API的异步封装?
如何在C#中优雅实现单线程API的异步包装(无第三方库)
我有一个必须在初始化它的线程上调用的API。希望编写一个包装器,创建一个线程来运行所有API调用,同时允许从UI线程通过await来调用这些方法。不使用第三方库的情况下,有没有优雅的实现方式?
预想实现效果
static class SingleThreadedApi { public static void Init(); // 必须与Init在同一线程调用 public static double LongRunningCall(); } class ApiWrapper { // ??? } class MyWindow { ApiWrapper api = new(); async void OnLoad() { await api.InitAsync(); } async void OnButtonClick() { var result = await api.LongRunningCallAsync(); answer.Text = $"result: {result}"; } }
注:本问题与仅要求任务串行执行的场景不同,核心要求是所有API调用必须在初始化时的同一线程执行。自定义SynchronizationContext是一种思路,但希望有更简洁的实现方式。
实现方案
可以通过创建专用线程+任务队列+TaskCompletionSource的组合,在不依赖第三方库的前提下实现需求。核心是让所有API操作都在同一个专用线程上执行,同时将同步操作包装为支持await的异步方法。
完整的ApiWrapper实现
class ApiWrapper : IDisposable { private readonly Thread _apiThread; private readonly BlockingCollection<(Delegate Action, TaskCompletionSource<object?> Tcs)> _taskQueue = new(); private bool _disposed; public ApiWrapper() { // 创建并启动专用后台线程 _apiThread = new Thread(RunApiProcessingLoop) { IsBackground = true, Name = "SingleThreadedApiWorker" }; _apiThread.Start(); } private void RunApiProcessingLoop() { try { // 持续从队列中取出任务执行 foreach (var taskEntry in _taskQueue.GetConsumingEnumerable()) { if (_disposed) break; try { // 在当前线程(即API初始化的线程)执行操作 var result = taskEntry.Action.DynamicInvoke(); taskEntry.Tcs.SetResult(result); } catch (Exception ex) { // 将异常传递给await的调用者 taskEntry.Tcs.SetException(ex.InnerException ?? ex); } } } finally { _taskQueue.Dispose(); } } // 通用包装方法:将无返回值的同步操作转为Task private Task EnqueueTask(Action action) { return EnqueueTask<object?>(() => { action(); return null; }); } // 通用包装方法:将有返回值的同步操作转为Task<T> private Task<T> EnqueueTask<T>(Func<T> action) { if (_disposed) throw new ObjectDisposedException(nameof(ApiWrapper)); var tcs = new TaskCompletionSource<object?>(); _taskQueue.Add((action, tcs)); // 转换返回结果的类型 return tcs.Task.ContinueWith( t => (T)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion); } // 包装SingleThreadedApi.Init方法 public Task InitAsync() { return EnqueueTask(SingleThreadedApi.Init); } // 包装SingleThreadedApi.LongRunningCall方法 public Task<double> LongRunningCallAsync() { return EnqueueTask(SingleThreadedApi.LongRunningCall); } public void Dispose() { if (!_disposed) { _disposed = true; _taskQueue.CompleteAdding(); // 等待线程结束(可根据业务需求调整等待时间或取消等待) _apiThread.Join(TimeSpan.FromSeconds(3)); } } }
实现细节说明
- 专用线程:所有API操作都在这个线程执行,确保与初始化线程一致,满足API的线程绑定要求。
- 线程安全队列:
BlockingCollection自带线程安全特性,UI线程可以安全添加任务,专用线程自动阻塞等待新任务。 - TaskCompletionSource:充当同步操作和异步
Task之间的桥梁,将操作结果或异常传递回调用者,支持await语法。 - 资源清理:实现
IDisposable接口,确保窗口关闭时能正确终止线程、清理队列,避免资源泄漏。
使用注意事项
- 在UI窗口关闭时,务必调用
ApiWrapper.Dispose()释放资源。 - 所有API包装方法都会串行执行,无需额外处理并发问题。
- 可以在
await时通过try/catch捕获API操作抛出的异常。
内容的提问来源于stack exchange,提问作者Don Reba
相关产品推荐
相关产品推荐

