我正在尝试使用SemaphoreSlim
.NET 4.0 中的新类来限制可以无限期运行的快节奏循环。在对此进行单元测试时,我发现如果循环足够紧密并且并行度足够高,则SemaphoreSlim
在调用时会抛出无法捕获的异常Release()
,即使您首先检查.Count
属性并在期间锁定信号量实例本身整个检查计数/释放序列。
此异常会关闭应用程序,期间。据我所知,没有抓住它。
深入挖掘,我发现在调用期间SemaphoreSlim
试图在.AvailableWaitHandle
内部访问它自己的属性Release()
,它在那里抛出异常,而不是我访问SemaphoreSlim
实例本身。(我必须使用 Debug->Exceptions->Common Language Runtime Exceptions->Thrown all check in Visual Studio 进行调试以发现这一点;您无法在运行时捕获它。有关更多详细信息,请参阅The Uncatchable Exception。)
我的问题是,有没有人知道在这种情况下使用此类而不冒立即终止应用程序的风险的防弹方法?
注意:信号量实例被包装在一个 RateGate 实例中,其代码可以在这篇文章中找到:Better Rate Limiting in .NET。
更新:我正在添加完整的控制台应用程序代码来重现。两个答案都有助于解决方案;请参阅下面的说明。
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;
}
}
}
因此,使用@dtb 的解决方案,线程“a”仍然有可能通过_isDisposed
检查,但线程“b”在线程“a”命中之前处理信号量Release()
。我发现在 ExitTimerCallback 和 Dispose 方法中都在 _semaphore 实例周围添加了一个锁。@Peter Ritchie 的建议使我在处理信号量之前另外取消并处理了计时器。这两件事的结合使程序可以无一例外地正确完成和处理 RateGate。
由于没有该输入我不会得到这个,我不想回答自己。但是由于 StackOverflow 在有完整答案时更有用,所以我会接受任何人首先发布成功在上面的控制台应用程序中存活的补丁或伪补丁。