我们使用监视程序来确定连接的系统是否仍然存在。
在前面的代码中,我们直接使用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
。