C# 在等待挂起的操作取消时处理信号量LIM安全吗?

C# 在等待挂起的操作取消时处理信号量LIM安全吗?,c#,semaphore,dispose,C#,Semaphore,Dispose,我不得不使用SemaphoreSlim来确保对代码的某些部分进行单线程访问,并希望确保正确处理所有内容。假设我有以下课程: public class Foo { private readonly CancellationTokenSource _canceller = new CancellationTokenSource(); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); ~Foo(

我不得不使用SemaphoreSlim来确保对代码的某些部分进行单线程访问,并希望确保正确处理所有内容。假设我有以下课程:

public class Foo
{
    private readonly CancellationTokenSource _canceller = new CancellationTokenSource();
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

    ~Foo()
    {
        Dispose(false);
        GC.SuppressFinalize(this);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        _canceller.Cancel();

        if (_disposing)
            _semaphore.Dispose();

        _disposed = true;
    }

    public async Task ExecuteAsync()
    {
        try
        {
            await _semaphore.WaitAsync(_canceller.Token);
        }
        catch (OperationCanceledException)
        {
            throw new ObjectDisposedException(nameof(Foo), "Cannot execute on a Foo after it has been disposed");
        }
        
        try
        {
            // Critical section
        }
        finally
        {
            _semaphore.Release();
        }
    }
}
当我调用
Dispose(true)
时,我有效地一行接一行地执行以下命令:

_canceller.Cancel();
_semaphore.Dispose();
我的问题是,当前等待关键部分的任何其他线程/任务会发生什么情况?我能保证他们总是先看到取消,这样就不会有信号量被处理的问题吗?到目前为止,这还没有引起任何问题,但这并不意味着它是安全的。

问题似乎非常具体

信号量LIM的所有公共和受保护成员都是线程安全的,可以从多个线程并发使用,但Dispose()除外,只有在信号量LIM上的所有其他操作完成后,才能使用Dispose()

确保所有其他操作都已完成的唯一真正方法是确保所有任务都已完成,如文档中的示例所示

public class Foo
{
    private readonly List<Task> _semaphoreTasks = new list<Task>();

    protected void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        _canceller.Cancel();

        if (_disposing) 
        {
            Task.WaitAll(tasks);
            _semaphore.Dispose();
        }

        _disposed = true;
    }

    public async Task ExecuteAsync()
    {
        try
        {
            var task = _semaphore.WaitAsync(_canceller.Token);
            _semaphoreTasks.Add(task);
            await task;
        // ...
公共类Foo
{
私有只读列表_semaphoreTasks=新列表();
受保护的无效处置(bool处置)
{
如果(_)
返回;
_canceler.Cancel();
如果(_)
{
Task.WaitAll(任务);
_Dispose();
}
_这是真的;
}
公共异步任务ExecuteAsync()
{
尝试
{
var task=_信号量.WaitAsync(_canceller.Token);
_添加(任务);
等待任务;
// ...

这很可能还需要确保在执行dispose时将内容添加到列表中。(可以将私有类变量设置为本地方法变量,然后将类变量设置为null)

我希望通过使用取消令牌,我可以保证所有其他操作都已完成。我不确定
SemaphoreSlim
类是否在取消等待操作后继续在内部执行其他代码,但我认为不会。替代方法是跟踪对该类的调用次数ExecuteAsync,并等待它们全部取消,然后继续处理(我不希望这样做),或Dispose不是线程安全的文档,并让调用者在处理之前等待。我只是想到在操作期间调用处理Dispose的其他类,如“Stream”和“Socket”。这是一个基于连接的类,我希望类似的行为,或只是将所有任务存储在列表中,如示例中所示,以及在处理之前,请等待它们,也如示例所示。@AndrewWilliamson:
我不确定在取消等待操作后,SemaphoreSlim类是否继续在内部执行其他代码
-
Cancel
只是启动了取消操作。其他代码仍在执行
SemaphoreSlim.wait
。可能该代码正在运行在<代码> SimaPraceSLIM之前处理。调用/<代码>被调用,也许不是。我会考虑不调用<代码> SimaPraceSLIM。dispose与否,以及我们是否应该依赖于这种特定于实现的细节,但我认为这里的优点大于缺点。感谢您的帮助。注意:从终结器调用
TaskCompletionSource是不安全的。取消