[英]Timeout wrapper for non-async method behaves inconsistently when large(ish) quantities are used in a loop
提示:本站为国内最大中英文翻译问答网站,提供中英文对照查看,鼠标放在中文字句上可显示英文原文。
我正在使用非异步的第三方库,但也可能需要比预期更长的时间或偶尔会无限期地完全阻塞(直到外部清除)。
代表用这个进行测试:
SlowWorkerResult SlowWorker(int i)
{
var delay = i % 2 == 0 ? TimeSpan.FromSeconds(2) : TimeSpan.FromSeconds(4);
Thread.Sleep(delay);
return new SlowWorkerResult();
}
class SlowWorkerResult
{
}
为了处理这些超时,我将调用包装在Task.Run
中并应用我写给它的扩展方法:
static class Extensions
{
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)
{
var cts = new CancellationTokenSource();
var delayTask = Task.Delay(timeout);
var result = await Task.WhenAny(task, delayTask);
if (result == delayTask)
{
throw new TimeoutException();
}
cts.Cancel();
return await task;
}
}
无论何时单独运行,它都能可靠地工作,即
async Task<(bool, int)> BigWorker(int i)
{
try
{
Console.WriteLine($"BigWorker Started - {i}");
//do some async work
await Task.CompletedTask;
//do some non-async work using the timeout extension
var slowWorkerResult = await Task.Run(() => SlowWorker(i)).TimeoutAfter(TimeSpan.FromSeconds(3));
//do some more async work
await Task.CompletedTask;
return (true, i);
}
catch (Exception ex)
{
return (false, i);
}
finally
{
Console.WriteLine($"BigWorker Finished - {i}");
}
}
我知道这实际上放弃了一个线程。 除非来自第三方库的支持不会很快出现(如果有的话),我没有其他方法来防止死锁。
但是,当我在并行循环中运行BigWorker
时,我得到了意想不到的结果(即某些会话超时,而我原本希望它们完成)。 例如,如果我将totalWorkers
设置为 10,我会得到平均分配的成功/失败,并且该过程按预期大约需要 3 秒。
async Task Main()
{
var sw = new Stopwatch();
sw.Start();
const int totalWorkers = 10;
var tasks = new ConcurrentBag<Task<(bool, int)>>();
Parallel.For(0, totalWorkers, i => tasks.Add(BigWorker(i)));
var results = await Task.WhenAll(tasks);
sw.Stop();
var success = results.Count(r => r.Item1);
var fails = results.Count(r => !r.Item1);
var elapsed = sw.Elapsed.ToString(@"ss\.ffff");
Console.WriteLine($"Successes: {success}\nFails: {fails}\nElapsed: {elapsed}");
}
将totalWorkers
设置为更大的数字,比如 100,会生成基本上随机的成功/失败次数,并且总时间会花费更长的时间。
我怀疑这是由于任务调度和线程池引起的,但是我不知道我需要做什么来补救它。 我怀疑自定义任务调度程序会以某种方式确保我的DoWork
包装任务和我的Task.Delay
同时执行。 现在看来, Task.Delay
偶尔会在相应的DoWork
包装任务之前启动/完成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.