C# 基于任务与基于线程的看门狗-但需要异步
我们正在使用看门狗来确定连接的系统是否仍然存在 在前面的代码中,我们直接使用TCP,并在单独的线程中处理看门狗。现在使用的是一种新服务,它使用gRPC提供数据 为此,我们尝试对任务使用异步接口,但基于任务的看门狗将失败 我编写了一个小程序,对代码进行了抽象并说明了问题。您可以在基于任务的看门狗和基于线程的看门狗之间切换,方法是用C# 基于任务与基于线程的看门狗-但需要异步,c#,multithreading,async-await,task,C#,Multithreading,Async Await,Task,我们正在使用看门狗来确定连接的系统是否仍然存在 在前面的代码中,我们直接使用TCP,并在单独的线程中处理看门狗。现在使用的是一种新服务,它使用gRPC提供数据 为此,我们尝试对任务使用异步接口,但基于任务的看门狗将失败 我编写了一个小程序,对代码进行了抽象并说明了问题。您可以在基于任务的看门狗和基于线程的看门狗之间切换,方法是用/注释掉第18行 演示包含导致问题的以下代码: async Task gRPCSendAsync(CancellationToken cancellationToken
/
注释掉第18行
演示包含导致问题的以下代码:
async Task gRPCSendAsync(CancellationToken cancellationToken = default) => await Task.Yield();
async Task gRPCReceiveAsync(CancellationToken cancellationToken = default) => await Task.Yield();
var start = DateTime.UtcNow;
await gRPCSendAsync(cancellationToken).ConfigureAwait(false);
await gRPCReceiveAsync(cancellationToken).ConfigureAwait(false);
var end = DateTime.UtcNow;
if ((end - start).TotalMilliseconds >= 100)
// signal failing
如果此代码用于Task.Run
中,则如果应用程序在其他任务中有大量cpu工作要做,则会发出失败信号
如果使用专用线程,看门狗将按预期工作,不会引发任何问题
我确实理解这个问题:等待后的所有代码都可能(如果尚未完成或不包含“真正的”等待)排队到线程池中。但是线程池还有其他事情要做,因此完成该方法需要花费太长时间
是的,简单的答案是:使用线程
但是使用线程限制了我们只能使用同步方法。无法从线程中调用异步方法。我创建了另一个,它显示在第一个await
之后的所有代码都将排队到线程bool,这样CallAsync().Wait()
将无法工作。(顺便说一句,这个问题要处理得多。)
我们有很多异步代码可以在这样的时间关键型操作中使用
所以问题是:有没有任何方法可以使用带有async/await的任务来执行该操作
也许我完全错了,创建一个基于任务的看门狗应该做得非常不同
思想
我考虑的是
System.Threading.Timer
,但异步发送和异步接收的问题无论如何都会导致该问题。以下是如何从包中使用Stephen Cleary的类,以便将异步工作流约束到专用线程:
await Task.Factory.StartNew(() =>
{
AsyncContext.Run(async () =>
{
await DoTheWatchdogAsync(watchdogCts.Token);
});
}, TaskCreationOptions.LongRunning);
对AsyncContext.Run
的调用将被阻止,直到提供的异步操作完成。由DoTheWatchdogAsync
创建的所有异步延续将由当前线程上的AsyncContext
进行内部处理。在上面的示例中,当前线程不是ThreadPool
线程,因为在构建包装器任务时使用了标记。您可以通过查询属性来确认这一点
如果您愿意,您可以使用传统的线程
构造函数,而不是非传统的任务.Factory.StartNew
+长时间运行
您是否考虑过线程池按需创建,例如调用ThreadPool.SetMinThreads(50,10)代码>在程序开始时?作为旁注,比较通过调用UtcNow
获得的DateTime
s不是测量持续时间的可靠方法,因为它取决于可进行非确定性调整的系统时钟。“我建议改用一种新的方法。”西奥多·佐利亚斯是的,我这样做了,但这不是真正的解决办法。这只会将问题稍微转移到未来。此外,并不是每个使用过的CPU都能提供64个线程,所以这将是线程转换成本和线程数量之间的一条走钢丝绳。@TheodorZoulias是的,我知道。我们的高效代码使用秒表,但这并不重要,我试图创建一个简单的演示来说明问题。@TheodorZoulias我们目前使用的是Nito.AsyncEx
,原因还有其他,没有理由反对上下文
。我以前没有试过,因为我不知道。在阅读了一些文档以了解它的功能之后,我将尝试一下。谢谢你的提示,大部分时间。这是迄今为止我看到的最好的希望。但问题是我必须删除.configurewait(false)代码>使其工作。如果使用.ConfigureAwait调用第43行和第44行(false)代码>它不工作。此刻我不明白为什么这不起作用。继续…@SebastianSchumann yeap,说得好。任何ConfigureAwait(false)
都会将延续发送到线程池
,因此您应该删除所有延续,以便留在AsyncContext
。由于@StephenCleary的解释,我接受了这个答案,因为使用AsyncContext
是我们的最佳选择。Thx.我应该注意,AsyncContext
解决方案仅在顶层有效。例如,如果方法grpcsendsync
和gRPCReceiveAsync
依赖ThreadPool
线程进行内部工作,那么使用AsyncContext
可能不会给您带来任何好处。