System.Timers.Timer在Console App中使用带有异步的Task.Run时,间歇性地不会触发

时间:2015-07-27 21:30:47

标签: c# multithreading async-await task-parallel-library

我正在使用一个控制台应用程序,我需要读取20个URI的批次,我发现通过完成所有任务并并行运行它们然后在不同的线程中对结果进行排序来提高速度(允许获取下一批次。)

在我当前使用的调用中,每个线程在获得响应流时都会阻塞,我也看到同一个方法public class Example { // / this is the description of the variable public String var = "hellooooo world"; } 的异步版本。

我知道使用异步Await and Async in same line而不是阻止来释放线程池有好处:

异步版

GetResponseAsync

阻止版本

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);
});

但是我在控制台应用程序上看到了异常版本的奇怪暂停,其中System.Timers.Timer已经过去的事件停止被调用了很多秒(当它应该每秒关闭时)。

阻塞版每秒运行大约3,500个项目,所有核心的CPU使用率约为30%。

async on每秒运行大约3,800个事件,CPU使用率略高于阻塞但不是很多(仅为5%)...但是我使用的计时器似乎暂停了大约10到15秒每分钟左右,我的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); }); 函数:

Main()

所以看起来计时器和线程池在使用异步时是多么相关(并且只有async,阻塞时没有暂停),或者可能没有,无论是出现任何想法,还是如何进一步诊断?

2 个答案:

答案 0 :(得分:2)

System.Timers.Timer 有一个(不那么)好的特性,可以将 Elapsed 事件发布到线程池中。如果所有线程都忙或阻塞,或者操作队列很长,Elapsed 事件将不会被触发,直到它获得资源来执行。

即使您使用 Timer.SynchronizedObject 属性也是如此 - 请参阅 https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.elapsed?view=net-5.0

System.Threading.Timer 也是如此。看起来 System.Windows.Threading.DispatcherTimer 可能会在 UI 线程上执行 - 但您不能在控制台应用中使用它。

所以我想唯一的方法是为计时器创建一个专用线程,并在那里使用 while(true) 进行 Thread.Sleep() 循环。

更新

“专用线程计时器”类看起来大致如此。注意,我是凭记忆写的,所以它可能不会像那样编译。

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

答案 1 :(得分:1)

  

计时器和线程池是如何相关的

你的怀疑是正确的。发生的事情基本上称为臭名昭着的线程饥饿,即所有线程都忙,所以ThreadPool没有足够的线程来运行事件委托。

在ASP.NET中运行后,autoConfig="True"会确保您获得足够的线程(在峰值的情况下并不总是如此),并确保您不受连接限制的约束。但是在控制台应用程序中,你必须自己做。

所以,只需添加此代码段,我敢打赌,您的问题就会消失:

ThreadPool.SetMinThreads(100, 100);
ServicePointManager.DefaultConnectionLimit = 1000;