基于任务与基于线程的看门狗 - 但需要异步 [英] Task based vs. thread based Watchdog - but async needed

查看:94
本文介绍了基于任务与基于线程的看门狗 - 但需要异步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在使用看门狗来确定连接的系统是否还活着.

We're using watchdogs to determine whether a connected system is still alive or not.

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

In the previous code we used TCP directly and treated the watchdog in a separate thread. Now is a new service used that provides it's data using gRPC.

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

For that we tried using the async interface with tasks but a task based watchdog will fail.

我编写了一个小的 DEMO 来抽象代码并说明问题.您可以通过用 // 注释掉第 18 行,在基于任务的看门狗和基于线程的看门狗之间切换.

I wrote a small DEMO that abstracts the code and illustrates the problem. You can switch between task based watchdog and thread based watchdog by commenting out line 18 with //.

该演示包含导致问题的以下代码:

The demo contains this code that causes the problem:

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 工作要做,它将发出失败信号.

If this code is used in Task.Run it will signal failing if the application has a lot cpu-work to do in other tasks.

如果使用专用线程,看门狗会按预期工作,不会出现问题.

If a dedicated thread is used the watchdog works as expected and no problem is raise.

我确实理解这个问题:await 之后的所有代码都可能(如果尚未完成或不包含真正的"await)排队到线程池中.但是线程池还有其他事情要做,导致方法执行时间过长.

I do understand the problem: All code after await may be (if not finished already or does not contain a "real" await) queued to the thread pool. But the thread pool has other things to do so that it took too long to finish the method.

是的,简单的答案是:使用线程.

Yes the simple answer is: USE THREAD.

但是使用线程限制我们只能使用同步方法.没有办法从线程中调用异步方法.我创建了另一个 sample 显示第一个 await 之后的所有代码都将排队等待线程bool 以便 CallAsync().Wait() 不起作用.(顺便说一句.这里.)

But using a thread limits us to only use synchronous methods. There is no way to call an async method out of a thread. I created another sample that shows that all code after first await will be queued to thread bool so that CallAsync().Wait() will not work. (Btw. that issue is much more handled here.)

我们有很多异步代码可能会在此类时间紧迫的操作中使用.

We're having a lot of async code that may be used within such time critical operations.

所以问题是:有没有办法使用带有 async/await 的任务来执行该操作?

So the question is: Is there any way to perform that that operations using tasks with async/await?

也许我完全错了,创建基于任务的看门狗应该以非常不同的方式完成.

Maybe I'm completely wrong and creating an task based watchdog should be done very differently.

想法

我在考虑 System.Threading.Timer 但异步发送和异步接收的问题无论如何都会导致这个问题.

I was thinking about System.Threading.Timer but the problem of async sending and async receiving will cause that problem anyways.

推荐答案

这里是如何使用 Stephen Cleary 的 AsyncContext 类来自 Nito.AsyncEx.Context 包,以将异步工作流限制为专用线程:

Here is how you could use Stephen Cleary's AsyncContext class from the Nito.AsyncEx.Context package, in order to constrain an asynchronous workflow to a dedicated thread:

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

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

The call to AsyncContext.Run will block until the supplied asynchronous operation is completed. All asynchronous continuations created by the DoTheWatchdogAsync will be processed internally by the AsyncContext on the current thread. In the above example the current thread is not a ThreadPool thread, because of the flag TaskCreationOptions.LongRunning used in the construction of the wrapper Task. You could confirm this by querying the property Thread.CurrentThread.IsThreadPoolThread.

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

If you prefer you could use a traditional Thread constructor instead of the somewhat unconventional Task.Factory.StartNew+LongRunning.

这篇关于基于任务与基于线程的看门狗 - 但需要异步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆