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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:25:02