基于任务与基于线程的看门狗-但需要异步



我们使用监视程序来确定连接的系统是否仍然存在。

在前面的代码中,我们直接使用TCP,并在单独的线程中处理看门狗。现在使用了一个新服务,它使用gRPC提供数据。

为此,我们尝试在任务中使用异步接口,但基于任务的看门狗会失败。

我写了一个小的DEMO,它抽象了代码并说明了问题。通过用//注释第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,但异步发送和异步接收的问题无论如何都会导致这个问题。

以下是如何使用Nito.AsyncEx.Context包中Stephen Cleary的AsyncContext类,以便将异步工作流约束到专用线程:

await Task.Factory.StartNew(() =>
{
AsyncContext.Run(async () =>
{
await DoTheWatchdogAsync(watchdogCts.Token);
});
}, TaskCreationOptions.LongRunning);

AsyncContext.Run的调用将被阻塞,直到所提供的异步操作完成为止。DoTheWatchdogAsync创建的所有异步延续将由当前线程上的AsyncContext内部处理。在上面的例子中,由于包装器Task的构造中使用的标志TaskCreationOptions.LongRunning,当前线程不是ThreadPool线程。您可以通过查询属性Thread.CurrentThread.IsThreadPoolThread来确认这一点。

如果您愿意,可以使用传统的Thread构造函数,而不是一些非常规的Task.Factory.StartNew+LongRunning

最新更新