System.Timers.Timer在使用Task时间歇性地经过而不触发.从控制台应用程序异步运行
本文关键字:控制台 运行 异步 应用程序 经过 Timer Timers Task 时间 System | 更新日期: 2023-09-27 18:04:53
我正在使用一个控制台应用程序,我有一批20个uri,我需要从中读取,我发现通过使所有任务并行运行,然后在不同的线程中完成对结果进行排序,从而大大提高了速度(允许获取下一批)。
在我目前使用的调用中,每个线程在获得响应流时阻塞,我还看到有相同方法的异步版本GetResponseAsync
。
我理解通过在同一行使用async Await和async而不是阻塞来释放线程池的好处:
<<p> 异步版本/strong>return Task.Run(async () =>
{
var uri = item.Links.Alternate();
var request = (HttpWebRequest)WebRequest.Create(uri);
var response = await request.GetResponseAsync();
var stream = response.GetResponseStream();
if (stream == null) return null;
var reader = new StreamReader(stream);
return new FetchItemTaskResult(reader.ReadToEnd(), index, uri);
});
<<p> 阻塞版本/strong> return Task<FetchItemTaskResult>.Factory.StartNew(() =>
{
var uri = item.Links.Alternate();
var request = (HttpWebRequest)WebRequest.Create(uri);
var response = request.GetResponse();
var stream = response.GetResponseStream();
if (stream == null) return null;
var reader = new StreamReader(stream);
return new FetchItemTaskResult(reader.ReadToEnd(), index, uri);
});
然而,我在控制台应用程序中看到奇怪的暂停与异步版本,其中一个System.Timers.Timer经过的事件停止被调用了许多秒(当它应该每秒关闭)。
阻塞处理器每秒运行约3,500个项目,所有内核的CPU使用率约为30%。
异步运行大约每秒3,800个事件,CPU使用率略高于阻塞,但不是很多(只有5%)…然而,我使用的计时器似乎每分钟暂停约10至15秒,在我的Main()
功能:
private static void Main(string[] args)
{
// snip some code that runs the tasks
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (source, e) =>
{
Console.WriteLine(DateTime.UtcNow);
// snip non relevant code
Console.WriteLine("Commands processed: " + commandsProcessed.Sum(s => s.Value) + " (" + logger.CommandsPerSecond() + " per second)");
};
timer.Start();
Console.ReadKey();
}
所以似乎定时器和线程池在使用异步(并且只有异步,阻塞时没有暂停)时有些相关,或者可能没有,无论哪种方式,任何想法是什么,请和如何进一步诊断?
你的怀疑是正确的。发生的事情基本上被称为臭名昭著的线程饥饿,即所有的线程都很忙,所以定时器和线程池在某种程度上是相关的
ThreadPool
将没有足够的线程来运行事件委托。
一旦在ASP中运行。. NET, autoConfig="True"
确保您获得足够的线程(在峰值的情况下并不总是如此),并且还确保您不受连接限制的约束。但在控制台应用中,你必须自己做这些。
所以只要添加这段代码,我打赌你的问题就会解决:
ThreadPool.SetMinThreads(100, 100);
ServicePointManager.DefaultConnectionLimit = 1000;
System.Timers.Timer
有一个(不太)好的特性,即将Elapsed
事件发布到线程池中。如果所有线程都很忙或阻塞,或者操作队列很长,则Elapsed
事件将不会被触发,直到它获得执行该操作的资源。
即使使用Timer.SynchronizedObject
属性也是如此-参见https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer.elapsed?view=net-5.0。
System.Threading.Timer
也是如此。看起来System.Windows.Threading.DispatcherTimer
可能会在UI线程上执行-但你不能在控制台应用程序中使用它。
所以我想唯一的方法是为计时器创建一个专用线程,并在那里与Thread.Sleep()
有一个while(true)
循环。
"专用线程计时器"类大致是这样的。注意,我是从内存中编写的,所以它可能不会像那样编译。
public sealed class DedicatedTimer
{
public DedicatedTimer(int interval, Action action)
{
mInterval = interval; // Create the field
mAction = action; // Create the field
var thr = new Thread(Runner, isBackground); // Check the docs on the background flag
thr.Start();
}
private void Runner()
{
while (true)
{
Thread.Sleep(mInterval);
action.Invoke();
}
}
}
使用后台线程可以让你忘记它,所以当程序停止运行时,它就会被杀死。根据具体情况,您可能需要自己的Dispose
。
作为读者的练习,您可以将计时器的创建和启动分开,您可以添加CancellationToken
,您可以使用计时器列表并计算每次触发后到下一行的延迟。
请记住,操作是在这个线程上执行的。如果你想避免它,使用SynchronizationContext
.