System.Timers.Timer Elapsed 在使用 Task.Run 和控制台应用程序异步时间歇性不触发

g18*_*18c 6 c# multithreading task-parallel-library async-await

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

在我当前使用的调用中,每个线程在获取响应流时都会阻塞,我还看到有一个相同方法的异步版本GetResponseAsync

我知道通过在同一行中使用 async Await 和 Async而不是阻塞来释放线程池有好处:

异步版本

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);
});
Run Code Online (Sandbox Code Playgroud)

屏蔽版本

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);
});
Run Code Online (Sandbox Code Playgroud)

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

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

async on 每秒运行大约 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();
}
Run Code Online (Sandbox Code Playgroud)

因此,在使用异步(并且只有异步,阻塞时没有暂停)时,计时器和线程池似乎有一些相关性,或者可能不是,无论哪种方式,请了解正在发生的事情以及如何进一步诊断?

Ali*_*tad 2

定时器和线程池有一些相关性

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

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

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

ThreadPool.SetMinThreads(100, 100);
ServicePointManager.DefaultConnectionLimit = 1000;
Run Code Online (Sandbox Code Playgroud)