我正在尝试使用.NET 4.0中的新SemaphoreSlim
类来限制可以无限期运行的快节奏循环。在单元测试中,我发现如果循环足够紧并且并行度足够高,SemaphoreSlim
会在您调用Release()
,时抛出无法捕获的异常,即使首先检查.Count
属性,并在整个检查计数/释放序列期间锁定信号量实例本身。
此异常会删除应用,期间。据我所知,没有抓住它。
深入挖掘,我发现SemaphoreSlim
试图在.AvailableWaitHandle
调用期间内部访问它自己的Release()
属性,它会在那里抛出异常,而不是从我访问{{} 1}}实例本身。 (我必须使用Debug-> Exceptions->公共语言运行时异常进行调试 - >抛出所有在Visual Studio中检查的内容以发现这一点;您无法在运行时捕获它。有关详细信息,请参阅The Uncatchable Exception。 )
我的问题是,是否有人知道使用此课程的防弹方式,而不会在这种情况下立即终止应用程序?
注意:信号量实例包含在RateGate实例中,其代码可以在本文中找到:Better Rate Limiting in .NET。
更新: 我正在添加完整的控制台应用程序代码来重现。这两个答案都有助于解决方案;见下面的解释。
SemaphoreSlim
因此,使用@ dtb的解决方案,线程“a”仍然可以通过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;
}
}
}
检查,但线程“b”在线程“a”命中_isDisposed
之前处理信号量。我发现在ExitTimerCallback和Dispose方法中的_semaphore实例周围添加了一个锁。 @Peter Ritchie的建议让我在处理信号量之前另外取消并处理了计时器。这两个组合让程序完成并妥善处理RateGate,没有例外。
因为如果没有这个输入我就不会得到这个,我不想回答自己。但是,当完整答案可用时,StackOverflow会更有用,我会接受任何人首先发布一个成功存在上述控制台应用程序的补丁或伪补丁。
答案 0 :(得分:4)
问题在于您正在使用的RateGate类。它有一个内部Timer,即使在RateGate实例被释放后也能运行它的代码。此代码包括调用已放置的SemaphoreSlim上的Release。
修正:
@@ -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);
}
答案 1 :(得分:1)
根据你的评论,听起来你生成了一堆线程来处理RateGate对象,在使用它完成这些线程之前就将它们处理掉。即,代码退出using块后,线程仍在运行。 更新:如果你做了你在评论中描述的内容;但是不要使用使用块你不会遇到问题。我目睹的例外实际上是一个ObjectDisposedException;如果在使用它完成代码之前处理了RateGate,这将是有意义的...