c# SemaphoreSlim保姆级教程

SemaphoreSlim 保姆级教程

目录

  1. [什么是 SemaphoreSlim](#什么是 SemaphoreSlim)
  2. 为什么需要它
  3. 核心概念
  4. 基本用法
  5. 实际应用场景
  6. 高级特性
  7. 常见陷阱与最佳实践
  8. 完整示例项目

什么是 SemaphoreSlim

SemaphoreSlim 是 .NET 中用于控制同时访问资源或代码段的线程数量的轻量级同步原语。

简单理解:就像一个门禁系统,允许同时最多 N 个人进入,超过的人需要排队等待。

csharp 复制代码
// 创建一个最多允许 1 个线程同时进入的信号量
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

Semaphore vs SemaphoreSlim

特性 Semaphore SemaphoreSlim
跨进程 ✅ 支持 ❌ 不支持
性能 较慢 快速(快约10倍)
异步支持 ❌ 不支持 支持 async/await
推荐场景 跨进程同步 进程内同步

为什么需要它

问题场景

csharp 复制代码
// ❌ 没有同步保护 - 会出现数据竞争
private int _counter = 0;

async Task BadExample()
{
    // 1000 个任务同时修改同一个变量
    var tasks = Enumerable.Range(0, 1000).Select(async _ =>
    {
        _counter++; // 不是原子操作!
        await Task.Delay(1);
    });
    
    await Task.WhenAll(tasks);
    Console.WriteLine(_counter); // 不一定等于 1000!
}

解决方案

csharp 复制代码
// ✅ 使用 SemaphoreSlim 保护
private int _counter = 0;
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

async Task GoodExample()
{
    var tasks = Enumerable.Range(0, 1000).Select(async _ =>
    {
        await _semaphore.WaitAsync();
        try
        {
            _counter++; // 现在安全了
            await Task.Delay(1);
        }
        finally
        {
            _semaphore.Release();
        }
    });
    
    await Task.WhenAll(tasks);
    Console.WriteLine(_counter); // 总是 1000
}

核心概念

1. 初始化参数

csharp 复制代码
// 参数1: initialCount - 初始可用数量
// 参数2: maxCount - 最大可用数量
private SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: 1, maxCount: 5);

2. 核心方法

csharp 复制代码
// 同步等待(阻塞线程)
_semaphore.Wait();
_semaphore.Wait(TimeSpan.FromSeconds(1));  // 超时等待
_semaphore.Wait(CancellationToken.None);   // 支持取消

// 异步等待(推荐,不阻塞线程)
await _semaphore.WaitAsync();
await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
await _semaphore.WaitAsync(CancellationToken.None);

// 释放信号量
_semaphore.Release();      // 释放一次
_semaphore.Release(3);     // 释放多次

// 查看当前状态
int available = _semaphore.CurrentCount;  // 当前可用数量
bool canEnter = _semaphore.Wait(0);       // 尝试立即进入(不等待)

3. 状态图

复制代码
初始: Count=3 (最多3人同时进入)
    
    线程A进入 → Wait() → Count=2
    线程B进入 → Wait() → Count=1  
    线程C进入 → Wait() → Count=0
    线程D进入 → Wait() → 阻塞等待
    
    线程A退出 → Release() → Count=1 → 唤醒线程D
    线程D进入 → Count=0

基本用法

1. 基础同步模式

csharp 复制代码
public class BasicUsage
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    
    // 同步版本(不推荐,会阻塞线程)
    public void SyncMethod()
    {
        _semaphore.Wait();
        try
        {
            // 临界区代码 - 同一时间只有一个线程能执行这里
            Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在执行");
            Thread.Sleep(1000);
        }
        finally
        {
            _semaphore.Release();
        }
    }
    
    // 异步版本(推荐)
    public async Task AsyncMethodAsync()
    {
        await _semaphore.WaitAsync();
        try
        {
            // 临界区代码 - 异步等待,不阻塞线程
            Console.WriteLine($"任务开始");
            await Task.Delay(1000);
            Console.WriteLine($"任务结束");
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

2. 带超时的等待

csharp 复制代码
public class TimeoutExample
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    
    public async Task TryExecuteWithTimeoutAsync()
    {
        // 最多等待 2 秒
        if (await _semaphore.WaitAsync(TimeSpan.FromSeconds(2)))
        {
            try
            {
                await ExecuteCriticalOperationAsync();
            }
            finally
            {
                _semaphore.Release();
            }
        }
        else
        {
            Console.WriteLine("获取锁超时,执行备用逻辑");
            await ExecuteFallbackLogicAsync();
        }
    }
    
    private async Task ExecuteCriticalOperationAsync()
    {
        await Task.Delay(1000);
        Console.WriteLine("关键操作执行完成");
    }
    
    private async Task ExecuteFallbackLogicAsync()
    {
        await Task.Delay(500);
        Console.WriteLine("备用逻辑执行完成");
    }
}

3. 支持取消操作

csharp 复制代码
public class CancellationExample
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    private CancellationTokenSource _cts;
    
    public async Task ExecuteWithCancellationAsync()
    {
        using var cts = new CancellationTokenSource();
        
        // 5秒后自动取消
        cts.CancelAfter(TimeSpan.FromSeconds(5));
        
        try
        {
            await _semaphore.WaitAsync(cts.Token);
            try
            {
                // 长时间运行的操作
                await LongRunningOperationAsync(cts.Token);
            }
            finally
            {
                _semaphore.Release();
            }
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("操作已被取消");
        }
    }
    
    private async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(1000, token);
            Console.WriteLine($"进度: {i * 10}%");
        }
    }
}

4. 允许多个并发

csharp 复制代码
public class MultiConcurrencyExample
{
    // 最多允许 3 个线程同时访问
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3);
    
    public async Task ProcessItemsAsync(List<string> items)
    {
        var tasks = items.Select(async item =>
        {
            await _semaphore.WaitAsync();
            try
            {
                await ProcessItemAsync(item);
            }
            finally
            {
                _semaphore.Release();
            }
        });
        
        await Task.WhenAll(tasks);
    }
    
    private async Task ProcessItemAsync(string item)
    {
        Console.WriteLine($"处理 {item} - 线程 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(1000); // 模拟处理
        Console.WriteLine($"完成 {item}");
    }
}

实际应用场景

场景1: API 限流

csharp 复制代码
public class ApiRateLimiter
{
    // 每秒最多 10 个请求
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10, 10);
    private readonly TimeSpan _resetInterval = TimeSpan.FromSeconds(1);
    private DateTime _lastResetTime = DateTime.UtcNow;
    
    public async Task<T> CallApiAsync<T>(Func<Task<T>> apiCall)
    {
        // 每秒重置一次
        var now = DateTime.UtcNow;
        if (now - _lastResetTime >= _resetInterval)
        {
            var currentCount = _semaphore.CurrentCount;
            var releaseCount = 10 - currentCount;
            if (releaseCount > 0)
            {
                _semaphore.Release(releaseCount);
            }
            _lastResetTime = now;
        }
        
        await _semaphore.WaitAsync();
        try
        {
            return await apiCall();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

// 使用示例
var rateLimiter = new ApiRateLimiter();
var tasks = Enumerable.Range(1, 100).Select(async i =>
{
    var result = await rateLimiter.CallApiAsync(async () =>
    {
        await Task.Delay(100);
        return $"请求 {i} 完成";
    });
    Console.WriteLine(result);
});
await Task.WhenAll(tasks);

场景2: 生产者-消费者队列

csharp 复制代码
public class ProducerConsumerQueue<T>
{
    private readonly SemaphoreSlim _itemSemaphore;
    private readonly SemaphoreSlim _consumerSemaphore = new SemaphoreSlim(1, 1);
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly int _maxQueueSize;
    
    public ProducerConsumerQueue(int maxConcurrentConsumers, int maxQueueSize = 100)
    {
        _itemSemaphore = new SemaphoreSlim(0, maxQueueSize);
        _maxQueueSize = maxQueueSize;
        
        // 启动消费者
        for (int i = 0; i < maxConcurrentConsumers; i++)
        {
            _ = ConsumeAsync();
        }
    }
    
    public async Task ProduceAsync(T item)
    {
        await _consumerSemaphore.WaitAsync();
        try
        {
            if (_queue.Count >= _maxQueueSize)
                throw new InvalidOperationException("队列已满");
            
            _queue.Enqueue(item);
            _itemSemaphore.Release();
        }
        finally
        {
            _consumerSemaphore.Release();
        }
    }
    
    private async Task ConsumeAsync()
    {
        while (true)
        {
            await _itemSemaphore.WaitAsync();
            
            T item;
            await _consumerSemaphore.WaitAsync();
            try
            {
                item = _queue.Dequeue();
            }
            finally
            {
                _consumerSemaphore.Release();
            }
            
            await ProcessItemAsync(item);
        }
    }
    
    private async Task ProcessItemAsync(T item)
    {
        Console.WriteLine($"处理: {item}");
        await Task.Delay(500);
    }
}

场景3: 数据库连接池

csharp 复制代码
public class DatabaseConnectionPool
{
    private readonly SemaphoreSlim _semaphore;
    private readonly Queue<DbConnection> _availableConnections = new Queue<DbConnection>();
    private readonly object _lock = new object();
    
    public DatabaseConnectionPool(int maxConnections)
    {
        _semaphore = new SemaphoreSlim(maxConnections, maxConnections);
        
        // 初始化连接池
        for (int i = 0; i < maxConnections; i++)
        {
            _availableConnections.Enqueue(CreateConnection());
        }
    }
    
    public async Task<DbConnection> GetConnectionAsync()
    {
        await _semaphore.WaitAsync();
        
        lock (_lock)
        {
            return _availableConnections.Dequeue();
        }
    }
    
    public void ReturnConnection(DbConnection connection)
    {
        lock (_lock)
        {
            _availableConnections.Enqueue(connection);
        }
        _semaphore.Release();
    }
    
    private DbConnection CreateConnection()
    {
        // 创建实际的数据库连接
        return new SqlConnection("connectionString");
    }
}

// 使用示例
public class DataService
{
    private readonly DatabaseConnectionPool _pool;
    
    public DataService(DatabaseConnectionPool pool)
    {
        _pool = pool;
    }
    
    public async Task<User> GetUserAsync(int id)
    {
        var connection = await _pool.GetConnectionAsync();
        try
        {
            // 使用连接查询数据
            await Task.Delay(100); // 模拟数据库查询
            return new User { Id = id, Name = $"User {id}" };
        }
        finally
        {
            _pool.ReturnConnection(connection);
        }
    }
}

场景4: 文件写入协调

csharp 复制代码
public class FileLogger
{
    private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
    private readonly string _logFilePath;
    
    public FileLogger(string logFilePath)
    {
        _logFilePath = logFilePath;
    }
    
    public async Task LogAsync(string message)
    {
        await _writeSemaphore.WaitAsync();
        try
        {
            await File.AppendAllTextAsync(_logFilePath, 
                $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}{Environment.NewLine}");
        }
        finally
        {
            _writeSemaphore.Release();
        }
    }
    
    public async Task<List<string>> ReadLogsAsync(int lineCount = 100)
    {
        await _writeSemaphore.WaitAsync();
        try
        {
            var lines = await File.ReadAllLinesAsync(_logFilePath);
            return lines.TakeLast(lineCount).ToList();
        }
        finally
        {
            _writeSemaphore.Release();
        }
    }
}

场景5: UI 线程安全操作(WPF)

csharp 复制代码
public class SafeUiUpdater
{
    private readonly SemaphoreSlim _uiSemaphore = new SemaphoreSlim(1, 1);
    private readonly Dispatcher _dispatcher;
    
    public SafeUiUpdater(Dispatcher dispatcher)
    {
        _dispatcher = dispatcher;
    }
    
    public async Task UpdateUIAsync(Action uiAction)
    {
        await _uiSemaphore.WaitAsync();
        try
        {
            await _dispatcher.InvokeAsync(uiAction);
        }
        finally
        {
            _uiSemaphore.Release();
        }
    }
    
    public async Task<T> GetUIValueAsync<T>(Func<T> getValueFunc)
    {
        await _uiSemaphore.WaitAsync();
        try
        {
            return await _dispatcher.InvokeAsync(getValueFunc);
        }
        finally
        {
            _uiSemaphore.Release();
        }
    }
}

// WPF ViewModel 中使用
public class MainViewModel : INotifyPropertyChanged
{
    private readonly SafeUiUpdater _uiUpdater;
    private string _status;
    
    public MainViewModel()
    {
        _uiUpdater = new SafeUiUpdater(Application.Current.Dispatcher);
    }
    
    public string Status
    {
        get => _status;
        set => _uiUpdater.UpdateUIAsync(() =>
        {
            _status = value;
            OnPropertyChanged();
        });
    }
    
    public async Task ProcessDataAsync()
    {
        await _uiUpdater.UpdateUIAsync(() => Status = "开始处理...");
        
        await Task.Delay(1000);
        
        await _uiUpdater.UpdateUIAsync(() => Status = "处理完成!");
    }
}

高级特性

1. 使用 WaitAsync 避免死锁

csharp 复制代码
// ❌ 错误的做法 - 可能导致死锁
public async Task DeadlockRisk()
{
    _semaphore.Wait(); // 同步等待,阻塞线程
    try
    {
        await SomeAsyncOperation(); // 如果这个操作需要同一线程,就会死锁
    }
    finally
    {
        _semaphore.Release();
    }
}

// ✅ 正确的做法 - 使用异步等待
public async Task NoDeadlock()
{
    await _semaphore.WaitAsync(); // 异步等待,不阻塞线程
    try
    {
        await SomeAsyncOperation(); // 安全
    }
    finally
    {
        _semaphore.Release();
    }
}

2. 链式等待

csharp 复制代码
public class ChainedSemaphoreExample
{
    private readonly SemaphoreSlim _semaphore1 = new SemaphoreSlim(1, 1);
    private readonly SemaphoreSlim _semaphore2 = new SemaphoreSlim(1, 1);
    
    public async Task ProcessAsync()
    {
        // 按照固定顺序获取锁,避免死锁
        await _semaphore1.WaitAsync();
        try
        {
            await _semaphore2.WaitAsync();
            try
            {
                await CriticalOperationAsync();
            }
            finally
            {
                _semaphore2.Release();
            }
        }
        finally
        {
            _semaphore1.Release();
        }
    }
    
    // 使用 using 模式(需要自定义释放器)
    public async Task UsingPatternAsync()
    {
        await using (await _semaphore1.WaitAsync().ConfigureAwait(false))
        {
            // 自动释放
            await CriticalOperationAsync();
        }
    }
}

// 扩展方法实现自动释放
public static class SemaphoreSlimExtensions
{
    public static async Task<SemaphoreReleaser> WaitAsync(this SemaphoreSlim semaphore)
    {
        await semaphore.WaitAsync();
        return new SemaphoreReleaser(semaphore);
    }
}

public struct SemaphoreReleaser : IDisposable, IAsyncDisposable
{
    private readonly SemaphoreSlim _semaphore;
    
    public SemaphoreReleaser(SemaphoreSlim semaphore)
    {
        _semaphore = semaphore;
    }
    
    public void Dispose()
    {
        _semaphore?.Release();
    }
    
    public ValueTask DisposeAsync()
    {
        Dispose();
        return default;
    }
}

3. 动态调整并发数

csharp 复制代码
public class AdaptiveConcurrencyLimiter
{
    private SemaphoreSlim _semaphore;
    private readonly object _lock = new object();
    
    public AdaptiveConcurrencyLimiter(int initialConcurrency)
    {
        _semaphore = new SemaphoreSlim(initialConcurrency, initialConcurrency);
    }
    
    public async Task UpdateConcurrencyAsync(int newMaxConcurrency)
    {
        lock (_lock)
        {
            var oldSemaphore = _semaphore;
            var newSemaphore = new SemaphoreSlim(newMaxConcurrency, newMaxConcurrency);
            
            // 转移等待中的任务(简化版,实际需要更复杂的逻辑)
            _semaphore = newSemaphore;
            
            oldSemaphore.Dispose();
        }
    }
    
    public async Task ExecuteAsync(Func<Task> action)
    {
        await _semaphore.WaitAsync();
        try
        {
            await action();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

常见陷阱与最佳实践

陷阱1: 忘记释放

csharp 复制代码
// ❌ 错误 - 忘记释放
await _semaphore.WaitAsync();
await DoWork(); // 如果这里抛出异常,信号量永远不会释放

// ✅ 正确 - 使用 try/finally
await _semaphore.WaitAsync();
try
{
    await DoWork();
}
finally
{
    _semaphore.Release();
}

陷阱2: 释放次数超过获取次数

csharp 复制代码
// ❌ 错误 - 释放过多
_semaphore.Wait();
_semaphore.Release();
_semaphore.Release(); // 异常!超过初始计数

// ✅ 正确 - 一一对应
_semaphore.Wait();
try
{
    // work
}
finally
{
    _semaphore.Release(); // 只释放一次
}

陷阱3: 在异步代码中使用 Wait()

csharp 复制代码
// ❌ 错误 - 可能导致死锁
async Task Bad()
{
    _semaphore.Wait(); // 阻塞线程
    await Task.Delay(1000); // 如果这需要原线程,死锁
    _semaphore.Release();
}

// ✅ 正确
async Task Good()
{
    await _semaphore.WaitAsync();
    await Task.Delay(1000);
    _semaphore.Release();
}

最佳实践清单

csharp 复制代码
public class BestPractices
{
    // ✅ 1. 使用 readonly 确保不会意外替换
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    
    // ✅ 2. 实现 IDisposable
    public class ResourceHandler : IDisposable
    {
        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
        private bool _disposed;
        
        public async Task ProcessAsync()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            await _semaphore.WaitAsync();
            try
            {
                await Task.Delay(100);
            }
            finally
            {
                _semaphore.Release();
            }
        }
        
        public void Dispose()
        {
            if (!_disposed)
            {
                _semaphore.Dispose();
                _disposed = true;
            }
        }
    }
    
    // ✅ 3. 使用 ConfigureAwait(false) 避免上下文切换
    public async Task LibraryMethodAsync()
    {
        await _semaphore.WaitAsync().ConfigureAwait(false);
        try
        {
            await Task.Delay(100).ConfigureAwait(false);
        }
        finally
        {
            _semaphore.Release();
        }
    }
    
    // ✅ 4. 指定合理的超时时间
    public async Task<bool> TryExecuteWithTimeoutAsync(TimeSpan timeout)
    {
        if (await _semaphore.WaitAsync(timeout))
        {
            try
            {
                await Task.Delay(100);
                return true;
            }
            finally
            {
                _semaphore.Release();
            }
        }
        return false;
    }
}

完整示例项目

多线程下载管理器

csharp 复制代码
public class DownloadManager
{
    private readonly SemaphoreSlim _concurrencyLimiter;
    private readonly ConcurrentDictionary<string, DownloadTask> _activeDownloads;
    private readonly IProgress<DownloadProgress> _progress;
    
    public DownloadManager(int maxConcurrentDownloads, IProgress<DownloadProgress> progress = null)
    {
        _concurrencyLimiter = new SemaphoreSlim(maxConcurrentDownloads, maxConcurrentDownloads);
        _activeDownloads = new ConcurrentDictionary<string, DownloadTask>();
        _progress = progress;
    }
    
    public async Task<string> DownloadFileAsync(string url, string savePath)
    {
        var taskId = Guid.NewGuid().ToString();
        var downloadTask = new DownloadTask { Url = url, SavePath = savePath, Id = taskId };
        
        _activeDownloads.TryAdd(taskId, downloadTask);
        
        // 等待获得下载许可
        await _concurrencyLimiter.WaitAsync();
        
        try
        {
            downloadTask.Status = DownloadStatus.Downloading;
            _progress?.Report(new DownloadProgress(taskId, 0, DownloadStatus.Downloading));
            
            // 模拟下载
            for (int i = 0; i <= 100; i += 10)
            {
                await Task.Delay(500);
                downloadTask.Progress = i;
                _progress?.Report(new DownloadProgress(taskId, i, DownloadStatus.Downloading));
                
                // 支持取消
                if (downloadTask.CancellationToken.IsCancellationRequested)
                {
                    downloadTask.Status = DownloadStatus.Cancelled;
                    throw new OperationCanceledException();
                }
            }
            
            downloadTask.Status = DownloadStatus.Completed;
            _progress?.Report(new DownloadProgress(taskId, 100, DownloadStatus.Completed));
            
            return savePath;
        }
        catch (Exception ex)
        {
            downloadTask.Status = DownloadStatus.Failed;
            downloadTask.Error = ex.Message;
            _progress?.Report(new DownloadProgress(taskId, downloadTask.Progress, DownloadStatus.Failed));
            throw;
        }
        finally
        {
            _activeDownloads.TryRemove(taskId, out _);
            _concurrencyLimiter.Release();
        }
    }
    
    public async Task CancelDownloadAsync(string taskId)
    {
        if (_activeDownloads.TryGetValue(taskId, out var task))
        {
            task.Cancel();
        }
    }
    
    public async Task<IEnumerable<DownloadTask>> GetActiveDownloadsAsync()
    {
        return _activeDownloads.Values.ToList();
    }
}

public class DownloadTask
{
    public string Id { get; set; }
    public string Url { get; set; }
    public string SavePath { get; set; }
    public int Progress { get; set; }
    public DownloadStatus Status { get; set; }
    public string Error { get; set; }
    private CancellationTokenSource _cts = new CancellationTokenSource();
    
    public CancellationToken CancellationToken => _cts.Token;
    public void Cancel() => _cts.Cancel();
}

public enum DownloadStatus
{
    Pending,
    Downloading,
    Completed,
    Failed,
    Cancelled
}

public record DownloadProgress(string TaskId, int Progress, DownloadStatus Status);

// 使用示例
class Program
{
    static async Task Main()
    {
        var progress = new Progress<DownloadProgress>(p =>
        {
            Console.WriteLine($"[{p.TaskId.Substring(0, 8)}] {p.Status}: {p.Progress}%");
        });
        
        var manager = new DownloadManager(maxConcurrentDownloads: 3, progress);
        
        var urls = Enumerable.Range(1, 10).Select(i => $"https://example.com/file{i}.zip").ToList();
        
        var tasks = urls.Select((url, index) => 
            manager.DownloadFileAsync(url, $"file{index}.zip"));
        
        await Task.WhenAll(tasks);
        
        Console.WriteLine("所有下载完成!");
    }
}

总结

核心要点

  1. SemaphoreSlim 用于控制并发数量,特别适合异步场景
  2. 始终使用 try/finally 确保释放信号量
  3. 优先使用 WaitAsync() 而不是 Wait(),避免死锁
  4. 合理设置超时时间,避免无限等待
  5. 实现 IDisposable 释放资源

选择指南

需求 推荐方案
保护单个资源(一次一个线程) new SemaphoreSlim(1, 1)
限制并发数量(如 API 限流) new SemaphoreSlim(5, 5)
生产者-消费者模式 结合 Queue 使用
跨进程同步 使用 Semaphore(不是 Slim)
高性能场景 SemaphoreSlim(比 lock 更快)

希望这个教程能帮助你完全掌握 SemaphoreSlim!记住:获取后务必释放,异步用 WaitAsync,同步用 try/finally

相关推荐
龙侠九重天2 小时前
ML.NET 实战:快速构建分类模型
分类·数据挖掘·c#·.net
|_⊙2 小时前
红黑树 (C++)
开发语言·c++·学习
LONGZETECH2 小时前
一线汽车教师实测:迈腾380TSI电气故障仿真软件,破解教学与大赛双重痛点
网络·科技·汽车·汽车仿真教学软件·汽车教学软件·新能源汽车仿真教学软件
Pixlout2 小时前
《7元接口体系》v1.0
网络·算法·硬件工程
Fate_I_C2 小时前
Kotlin 内部类和嵌套类
java·开发语言·kotlin
loockluo2 小时前
NFS网络存储部署与性能优化实战:家用服务器的学习与实践
服务器·网络·性能优化
昵称暂无12 小时前
低代码平台深度测评:OutSystems vs Mendix谁更胜一筹
开发语言·低代码
灰子学技术2 小时前
Envoy 中 TCP 网络连接实现分析
运维·服务器·网络·网络协议·tcp/ip
We་ct2 小时前
JS手撕:函数进阶 & 设计模式解析
开发语言·前端·javascript·设计模式·面试·前端框架