C# 如何调用SemaphoreSlim.Release()而不冒应用程序失败的风险?

C# 如何调用SemaphoreSlim.Release()而不冒应用程序失败的风险?,c#,parallel-processing,try-catch,semaphore,rategate,C#,Parallel Processing,Try Catch,Semaphore,Rategate,我试图使用.NET4.0中新的SemaphoreSlim类来限制可以无限期运行的快节奏循环。在单元测试中,我发现如果循环足够紧密并且并行度足够高,SemaphoreSlim可以在调用Release()时抛出不可跟踪的异常,即使在整个检查计数/释放序列中先检查.Count属性并锁定信号量实例本身 此异常会关闭应用程序,句号。据我所知,没有人能抓住它 深入挖掘,我发现SemaphoreSlim正试图访问它自己的。在Release()调用期间,它会在内部抛出异常,而不是从我访问SemaphoreSli

我试图使用.NET4.0中新的
SemaphoreSlim
类来限制可以无限期运行的快节奏循环。在单元测试中,我发现如果循环足够紧密并且并行度足够高,
SemaphoreSlim
可以在调用
Release()
时抛出不可跟踪的异常,即使在整个检查计数/释放序列中先检查
.Count
属性并锁定信号量实例本身

此异常会关闭应用程序,句号。据我所知,没有人能抓住它

深入挖掘,我发现
SemaphoreSlim
正试图访问它自己的
。在
Release()
调用期间,它会在内部抛出异常,而不是从我访问
SemaphoreSlim
实例本身。(我必须使用调试->异常->公共语言运行时异常->抛出Visual Studio中的所有已检查项进行调试才能发现此问题;您在运行时无法捕获它。有关详细信息,请参阅。)

我的问题是,在这种情况下,有没有人知道一种防弹的方法来使用这个类而不冒立即终止应用程序的风险

注意:信号量实例包装在RateGate实例中,其代码可在本文中找到:

更新: 我正在添加完整的控制台应用程序代码以进行复制。这两个答案都有助于解决问题;请参见下面的解释

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Linq;
using System.Text;
using System.Threading;
using PennedObjects.RateLimiting;

namespace RateGateForceTerminateDemo
{
    class Program
    {
        static int _secondsToRun = 10;
        static void Main(string[] args) {
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            OptimizeMaxThreads();
            Console.WriteLine();
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey(true);
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
            Console.WriteLine("Unhandled exception, terminating={0}:{1}", e.IsTerminating, e.ExceptionObject.ToString());
            Console.WriteLine("Press any key to terminate app.");
            Console.ReadKey(true);
        }

        static void OptimizeMaxThreads() {
            int processors = Environment.ProcessorCount;
            int processorsSq = Convert.ToInt32(Math.Pow(processors,2));
            int threads = 1;
            double result;
            Tuple<int, double> maxResult = new Tuple<int, double>(threads, 0);
            while (threads <= processorsSq) {
                Console.WriteLine("Running for {0}s with upper limit of {1} threads... ", _secondsToRun, threads);
                result = TestThrottling(10000000, threads, _secondsToRun);
                Console.WriteLine("Ok. Result is {0:N0} ops/s", result);
                Console.WriteLine();
                if(result > maxResult.Item2)
                    maxResult = new Tuple<int, double>(threads, result);
                threads *= 2;
            }
            Console.WriteLine("{0} threads achieved max throughput of {1:N0}", maxResult.Item1, maxResult.Item2);
        }
        static double TestThrottling(int limitPerSecond, int maxThreads, int maxRunTimeSeconds) {
            int completed = 0;
            RateGate gate = new RateGate(limitPerSecond, TimeSpan.FromSeconds(1));
            ParallelLoopResult res = new ParallelLoopResult();
            ParallelOptions parallelOpts = new ParallelOptions() { MaxDegreeOfParallelism = maxThreads };
            Stopwatch sw = Stopwatch.StartNew();
            try {
                res = Parallel.For<int>(0, 1000000000, parallelOpts, () => 0, (num, state, subtotal) =>
                {
                    bool succeeded = gate.WaitToProceed(10000);
                    if (succeeded) {
                        subtotal++;
                    }
                    else {
                        Console.WriteLine("Gate timed out for thread {0}; {1:N0} iterations, elapsed {2}.", Thread.CurrentThread.ManagedThreadId, subtotal, sw.Elapsed);
                        // return subtotal;
                    }
                    if (sw.Elapsed.TotalSeconds > maxRunTimeSeconds) {
                        Console.WriteLine("MaxRunTime expired for thread {0}, last succeeded={1}, iterations={2:N0}, elapsed={3}.", Thread.CurrentThread.ManagedThreadId, succeeded, subtotal, sw.Elapsed);
                        state.Break();
                    }
                    return subtotal;
                }, (subtotal) => Interlocked.Add(ref completed, subtotal));
            }
            catch (AggregateException aggEx) {
                Console.WriteLine(aggEx.Flatten().ToString());
            }
            catch (Exception ex) {
                Console.WriteLine(ex);
            }
            sw.Stop();
            double throughput = completed / Math.Max(sw.Elapsed.TotalSeconds, 1);
            Console.WriteLine("Done at {0}, finished {1:N0} iterations, IsCompleted={2}, LowestBreakIteration={3:N0}, ",
                sw.Elapsed,
                completed,
                res.IsCompleted,
                (res.LowestBreakIteration.HasValue ? res.LowestBreakIteration.Value : double.NaN));
            Console.WriteLine();
            //// Uncomment the following 3 lines to stop prevent the ObjectDisposedException:
            //Console.WriteLine("We should not hit the dispose statement below without a console pause.");
            //Console.Write("Hit any key to continue... ");
            //Console.ReadKey(false);
            gate.Dispose();
            return throughput;
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用System.Threading.Tasks;
使用System.Linq;
使用系统文本;
使用系统线程;
使用PennedObject.RateLimiting;
命名空间RateGateForceTerminateDemo
{
班级计划
{
静态整数_secondsToRun=10;
静态void Main(字符串[]参数){
AppDomain.CurrentDomain.UnhandledException+=新的UnhandledExceptionEventHandler(CurrentDomain\u UnhandledException);
优化MaxThreads();
Console.WriteLine();
控制台。WriteLine(“按任意键退出”);
Console.ReadKey(true);
}
静态无效CurrentDomain_UnhandledException(对象发送方,UnhandledExceptionEventArgs e){
WriteLine(“未处理的异常,终止={0}:{1}”,e.IsTerminating,e.ExceptionObject.ToString());
Console.WriteLine(“按任意键终止应用程序”);
Console.ReadKey(true);
}
静态无效优化MaxThreads(){
int processors=Environment.ProcessorCount;
int processorsSq=转换为32(数学功率(处理器,2));
int线程=1;
双重结果;
Tuple maxResult=新的Tuple(线程,0);
while(线程maxResult.Item2)
maxResult=新元组(线程,结果);
螺纹*=2;
}
WriteLine(“{0}个线程实现了{1:N0}”、maxResult.Item1、maxResult.Item2)的最大吞吐量;
}
静态双测试节流(int-limitPerSecond、int-maxThreads、int-maxRunTimeSeconds){
int completed=0;
速率门=新速率门(limitPerSecond,TimeSpan.FromSeconds(1));
ParallelLoopResult res=新的ParallelLoopResult();
PARALLEOPTIONS PARALLEOPTS=new PARALLEOPTIONS(){MAXDEGEEOFPARALLELISM=MAXTREADS};
秒表sw=Stopwatch.StartNew();
试一试{
res=Parallel.For(0,1000000000,parallelOpts,()=>0,(num,state,subtotal)=>
{
bool successed=gate.WaitToProceed(10000);
如果(成功){
小计++;
}
否则{
WriteLine(“线程{0}的门超时;{1:N0}迭代,经过的{2}.”,thread.CurrentThread.ManagedThreadId,小计,sw.eassed);
//返回小计;
}
如果(sw.appeased.TotalSeconds>maxRunTimeSeconds){
WriteLine(“线程{0}的MaxRunTime已过期,最后一次成功={1},迭代次数={2:N0},经过时间={3}.”,thread.CurrentThread.ManagedThreadId,Successed,subtotal,sw.Expressed);
state.Break();
}
返回小计;
},(小计)=>联锁。添加(参考已完成,小计));
}
捕获(聚合异常聚集){
Console.WriteLine(aggEx.flatte().ToString());
}
捕获(例外情况除外){
控制台写入线(ex);
}
sw.Stop();
双倍吞吐量=已完成/数学最大值(sw.eassed.TotalSeconds,1);
WriteLine(“在{0}完成,完成{1:N0}次迭代,IsCompleted={2},LowestBreakIteration={3:N0},”,
西南过去了,
完整的,
决议草案已完成,
(res.LowestBreakIteration.HasValue?res.LowestBreakIteration.Value:double.NaN));
Console.WriteLine();
////取消对以下3行的注释以停止阻止ObjectDisposedException:
//WriteLine(“如果没有控制台暂停,我们不应该点击下面的dispose语句。”);
//控制台。写入(“按任意键继续…”);
//Console.ReadKey(false);
gate.Dispose();
返回吞吐量;
}
}
}
因此,使用@dtb的解决方案,线程“a”仍然有可能通过
\u isDisposed
检查,但让线程“b”在线程“a”点击
释放()之前处理信号量。我发现在ExitTimeCallback和Dispose方法中都在_信号量实例周围添加了锁@彼得·里奇的建议让我另外取消并处理了计时器,b
@@ -88,7 +88,8 @@
    int exitTime;
    while (_exitTimes.TryPeek(out exitTime)
            && unchecked(exitTime - Environment.TickCount) <= 0)
    {
+       if (_isDisposed) return;
        _semaphore.Release();
        _exitTimes.TryDequeue(out exitTime);
    }